MyBatis 常用工具类
SQL 类
MyBatis 提供了一个 SQL 工具类,使用这个工具类,我们可以很方便在 Java 代码动态构建 SQL 语句
String newSql = new SQL() ({
SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL NAME");
SELECT("P.LAST NAME, P.CREATED ON, P.UPDATED ON");
FROM("PERSON P");
FROM("ACCOUNT A");
INNER JOIN("DEPARTMENT D ON DID=P.DEPARTMENT ID");
INNER JOIN("COMPANY C On D.COMPANY ID=C.ID");
WHERE("P.ID = A.ID");
WHERE("P.FIRST NAME like ?");
OR();
WHERE("P.LAST NAME like ?");
GROUP BY("P.ID");
HAVING("P.LAST_NAME like ?");
OR();
HAVING("P.FIRST NAME like ?");
ORDER BY("P.ID");
ORDER BY("P.FULL NAME");
}}.tostring();
如上面的代码所示,创建了一个匿名的 SQL 类的子类,在匿名子类的初始化代码块中,调用 SELECT()、FROM() 等方法构建 SQL 语句,这种方式能够很好地避免字符串拼接过程中缺少空格或者偶然间重复出现的 AND 关键字导致的 SOL 语句不正确
除了 SELECT 语句外,SQL 工具类也可以用作构建 UPDATE、INSERT 等语句
@Test
public void testInsertSql() {
String insertSql = new SQL().
INSERT INTO("PERSON").
VALUES("ID, FIRST NAME","#{id}, #{firstName}").
VALUES("LAST NAME","#(lastName}").toString();
System.out.println(insertSal);
}
@Test
public void testDeleteSql() {
String deleteSql= new SQL() {{
DELETE FROM("PERSON");
WHERE("ID = #{id)");
}}.toString();
System.out.println(deleteql);
}
@Test
public void testUpdateSql() {
String updateSql= new SQL() {{
UPDATE("PERSON");
SET("FIRST NAME = #{firstName}");
WHERE("ID = #{id}");
}}.toString();
System.out.println(updateSql);
}
使用 SQL 工具类的另一个好处是可以很方便地在 Java 代码中根据条件动态地拼接 SQL 语句
public String selectPerson(final String id, final String firstName, final String lastName) {
return new SQL() {{
SELECT("P.ID, P.USERNAME, P.PASSWORD");
SELECT("P.FIRST_NAME, P.LAST NAME");
FROM("PERSON P");
if (id != null) {
WHERE("P.ID=#{id}");
}
if (firstName != null) {
WHERE("P.FIRST_NAME=#{firstName}");
}
if (lastName != null) {
WHERE("P.LAST_NAME=#{lastName}");
}
}}.toString();
}
ScriptRunner
该工具类用于读取脚本文件中的 SQL 语句并执行
public void testscriptRunner() {
try {
Connection connection=DriverManager.getConnection("jdbc:hsqldb:mem:mybatis","sa","");
Scriptrunner scriptRunnermnew ScriptRunner(connection):
scriptrunner.runScript(Resources.getResourceAsReader("create-table.sql"))
} catch (Exception e) {
e.printstackTrace();
}
}
如上面的代码所示,ScriptRunner 工具类的构造方法需要一个 java.sql.Connection 对象作为参数。创建 ScriptRunner 对象后,调用该对象的 runScript 方法即可,该方法接收一个读取 SQL 脚本文件的 Reader 对象作为参数
ScriptRunner 工具类中提供了一些属性,用于控制执行 SQL 脚本的一些行为,代码如下;
public class ScriptRunner{
// SQL异常是否中断程序执行
private boolean stopOnError;
// 是否抛出 SQLWarning 警告
private boolean throwWarning;
//是否自动提交
private boolean autoCommit;
// 属性为 true 时,批量执行文件中的 SQL 语句
//为false时逐条执行 sQL 语句,默认情况下,SQL语句以分号分割
private boolean sendfullScript;
//是否去除Windows系统换行符中的\r
private boolean removecrs;
//设置statement属性是否支持转义处理
private boolean escapeProcessing=true;
// 日志输出位置,默认标准输入输出,即控制台
private PrintWriter logWriter=new PrintWriter(System.out);
// 错误日志输出位置,默认控制台
private PrintWriter errorLoqWriter =new PrintWriter(System.err);
// 脚本文件中 SOL 语句的分隔符,默认为分号
private String delimiter= DEFAULT_DELIMITER;
// 是否支持 SOL 语句分割符,单独占一行
private boolean fullLineDelimiter;
...
}
我们可以直接调用这些属性对应的 Setter 方法来控制 ScriptRunner 工具类执行 SQL 脚本的行为
SqlRunner
MyBatis 提供了一个非常实用的、用于操作数据库的 SqlRunner 工具类,该类对 JDBC 做了很好的封装,结合 SQL 工具类,能够很方便地通过 Java 代码执行 SQL 语句并检索 SQL 执行结果
SglRunner 类提供了几个操作数据库的方法,分别说明如下:
- SqlRunner#closeConnection: 用于关闭 Connection 对象
- SqlRunner#selectOne(String sql, Object… args):执行 SELECT 语句,SQL 语句中可以使用占位符,如果 SOL 中包含占位符,则可变参数用于为参数占位符赋值,该方法只返回一条记录,若查询结果行数不等于一,则会抛出 SQLException 异常
- SqlRunner#selectAll(String sql,Object… args):该方法和 selectOne 方法的作用相同,只不过该方法可以返回多条记录,方法返回值是一个 List 对象,List 中包含多个 Map 对象,每个 Map 对象对应数据库中的一行记录
- SqlRunner#insert(String sql,Object… args):执行一条 INSERT 语句,插入一条记录
- SqlRunner#update(String sql,Object… args):更新若干条记录
- SqlRunner#delete(String sql,Object… args):删除若干条记录
- SqlRunner#run(String sql):执行任意一条 SQL 语句,最好为 DDL 语句
接下来我们来看一下 SqlRunner 工具类的使用案例,代码如下:
@Test
public void testselectOne() throws SQLException {
SqlRunner sqlRunner=new SqlRunner(connection)
String gryUserSql = new SQL() {{
SELECT("*");
FROM("user");
WHERE("id=?");
}}.toString();
Map<String, Object> resultMap = sqlRunner.selectOne(qryUserSql, Integer.valueOf(1));
System.out.println(JSON.toJSONString(resultMap));
}
@Test
public void testDelete() throws SQLException {
SqlRunner sqlRunner=new SqlRunner(connection);
String deleteUserSql = new SQL() {{
DELETE FROM("user");
WHERE("id = ?");
}}.toString();
sqlRunner.delete(deleteUserSql,Integer.valueOf(1));
}
@Test
public void testUpdate() throws SQLException {
SqlRunner sqlRunner=new SqlRunner(connection);
String updateUserSql = new SQL() {{
UPDATE("user");
SET("nick_name = ?");
WHERE("id = ?");
}}.toString();
sqlRunner.update(updateUserSql,"Jane",Integer.valueOf(1));
}
@Test
public void testInsert() throws SQLException {
SqlRunner sqlRunner=new SqlRunner(connection);
String insertUserSql = new SQL() {{
INSERT INTO("user");
INTO COLUMNS("create_time, name, password, phone, nick_name");
INTO VALUES("?,?,?,?,?");
}}.toString();
sqlRunner.insert(insertUserSql,createTime,"Jane","test","18700000000" "J");
}
MetaObject
MetaObject 是 MyBatis 中的反射工具类,该工具类在 MyBati s源码中出现的频率非常高。使用 MetaObject 工具类,我们可以很优雅地获取和设置对象的属性值
使用 MetaObject 工具类获取 User 对象的属性信息,案例代码如下:
@Test
public void testMetaObject() {
List<order> orders= new ArrayList()(
add(new Order("order20171024010246","《MyBatis源码深度解析》图书"));
add(new Order("order20171024010248","《AngularJs入门与进阶》图书"));
};
User user = new User(orders,"江荣波",3);
MetaObject metaObject = SystemMetaObject.forObject(user);
// 获取第一笔订单的商品名称
System.out.println(metaObject.getValue("orders[0].goodsName"));
// 获取第二笔订单的商品名称
System.out.println(metaObject.getvalue("orders[1].goodsName"));
// 为属性设置值
metaObject.setValue("orders[1].orderNo","order20181113010139");
// 判断 User 对象是否有 orderNo 属性
System.out.println("是否有orderNo属性且orderNo属性有对应的Getter方法:" + metaObject.hasGetter("orderNo"));
// 判断 User 对象是否有 name 属性
System.out.println("是否有name属性且name 属性有对应的Getter方法:" + metaObject.hasGetter("name"));
}
MetaClass
MetaClass 是 MyBatis 中的反射工具类,与 MetaOjbect 不同的是,MetaObject 用于获取和设置对象的属性值,而 MetaClass 则用于获取类相关的信息
@Test
public void testMetaClass() {
MetaClass metaClass =MetaClass.forClass(Orderclass,newDefaultReflectorFactory());
// 获取所有有 Getter 方法的属性名
String[] getterNames = metaClass.getGetterNames();
System.out.println(JSON.toJSONString(getterNames));
// 是否有默认构造方法
System.out.println("是否有默认构造方法:" + metaClass.hasDefaultConstructor());
// 某属性是否有对应的Getter/Setter方法
System.out.printIn("orderNo属性是否有对应的Getter 方法:" + metaClass.hasGetter("orderNo"));
System.out.println("orderNo属性是否有对应的Setter方法:" + metaClass.hasSetter("orderNo"));
System.out.println("orderNo属性类型:" + metaClass.getGetterType("orderNo"));
}
ObjectFactory
ObjectFactory 是 MyBatis 中的对象工厂,MyBatis 每次创建 Mapper 映射结果对象的新实例时,都会使用一个对象工厂实例来完成。ObjectFactory 接口只有一个默认的实现,即 DefaultObjectFactory,默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化
public class ObjectFactoryExample {
@Test
public void testObjectFactory() {
ObjectFactory objectFactory = new DefaultObjectFactory();
List<Integer> list = objectFactory.create(List.class);
Map<String, String> map = objectFactory.create(Map.class);
list.addAll(Arrays.asList(1. 23));
map.put("test","test");
System.out.printin(list);
System.out.println(map);
}
}
MyBatis 中使用 ObjectFactory 实例创建 Mapper 映射结果对象的目的是什么呢?实际上,这是 MyBatis 提供的一种扩展机制。有些情况下,在得到映射结果之前我们需要处理一些逻辑,或者在执行该类的有参构造方法时,在传入参数之前,要对参数进行一些处理,这时我们可以通过自定义 ObjectFactory 来实现。下面是一个自定义 ObjectFactory 的案例,代码如下:
public class CustomObjectfactory extends Defaultobjectfactory{
@Override
public object create(Class type){
if(type.equals(User.class)) {
//实例化User类
User user=(User)super.create(type);
user.setUuid(UUID.randomUUID().toString());
return user;
}
return super.create(type);
}
}
如上面的代码所示,自定义一个 ObjectFactory 非常简单,我们可以继承 DefaultObjectFactory,然后重写 create 方法即可。自定义 ObjectFactory 后,还需要在MyBatis 主配置文件中通过
<objectfactory type="com.blog4java.mybatis.objectfactory.CustomobjectFactory">
<property name="someProperty" value="10"/>
</objectfactory>
ProxyFactory
ProxyFactory 是 MyBatis 的代理工厂,主要用于创建动态代理对象,ProxyFactory接口有两个不同的实现,分别为 CglibProxyFactory 和 JavassistProxyFactory。从实现类的名称可以看出,MyBatis 支持两种动态代理策略,分别为 Cglib 和 Javassist 动态代理。ProxyFactory主要用于实现 MyBatis 的懒加载功能。当开启懒加载后,MyBatis 创建 Mapper 映射结果对象后,会通过 ProxyFactory 创建映射结果对象的代理对象。当我们调用代理对象的 Getter 方法获取数据时,会执行 CglibProxyFactory 或 JavassistProxyFactory 中定义的拦截逻辑,然后执行一次额外的查询
下面是使用 JavassistProxyFactory 创建动态代理对象的案例,代码如下:
public class ProxyFactoryExample {
@Data
@AllArgsConstructor
private static class Order {
private String orderNo;
private String goodsName;
}
@Test
public void testProxyFactory() {
// 创建 ProxyFactory对象
ProxyFactory proxyFactory =new JavassistProxyFactory();
Order order = new Order("gn20170123","《MyBatis源码深度解析》图书");
ObjectFactory objectFactory=new DefaultObjectFactory();
//调用ProxyFactory对象的createProxy()方法创建代理对象
Object proxyOrder=proxyFactory.createProxy(order,
mock(ResultLoaderMap.class),
mock(Configuration.class),
objectfactory,
Arrays.asList(String.class,String.class),
Arrays.asList(order.getorderNo(),order.getGoodsName()));
System.out.println(proxyOrder.getClass());
System.out.println(((Order)proxyOrder).getGoodsName());
}
}