Mybatis源码-常用工具类
目录
开始讲Mybatis相关常用工具类的介绍。每个都是在源码中有应用到的。
SQL #
作用:方便动态的生成语句。
public class SQLExample {
@Test
public void testSelectSQL() {
String orgSql = "SELECT P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME, P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON\n" +
"FROM PERSON P, ACCOUNT A\n" +
"INNER JOIN DEPARTMENT D on D.ID = P.DEPARTMENT_ID\n" +
"INNER JOIN COMPANY C on D.COMPANY_ID = C.ID\n" +
"WHERE (P.ID = A.ID AND P.FIRST_NAME like ?) \n" +
"OR (P.LAST_NAME like ?)\n" +
"GROUP BY P.ID\n" +
"HAVING (P.LAST_NAME like ?) \n" +
"OR (P.FIRST_NAME like ?)\n" +
"ORDER BY P.ID, P.FULL_NAME";
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 D.ID = 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();
assertEquals(orgSql, newSql);
}
@Test
public void testDynamicSQL() {
selectPerson(null,null,null);
}
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}");
}
ORDER_BY("P.LAST_NAME");
}}.toString();
}
@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(insertSql);
}
@Test
public void testDeleteSql() {
String deleteSql = new SQL() {{
DELETE_FROM("PERSON");
WHERE("ID = #{id}");
}}.toString();
System.out.println(deleteSql);
}
@Test
public void testUpdateSql() {
String updateSql = new SQL() {{
UPDATE("PERSON");
SET("FIRST_NAME = #{firstName}");
WHERE("ID = #{id}");
}}.toString();
System.out.println(updateSql);
}
}
源码分析 #
SQL类继承了AbstractSQL,主要的实现逻辑在抽象类中,
AbstractSQL类中持有了SQLStatement的实例,看看SQLStatement定义可知,这就是主要的一个SQL拼接实现类。
private static class SQLStatement {
// SQL语句的类型
public enum StatementType {
DELETE, INSERT, SELECT, UPDATE
}
// 用于判断SQL的类型。
StatementType statementType;
// 用于记录SQL实例SELECT()、UPDATE()等方法调用参数
List<String> sets = new ArrayList<String>();
List<String> select = new ArrayList<String>();
List<String> tables = new ArrayList<String>();
List<String> join = new ArrayList<String>();
List<String> innerJoin = new ArrayList<String>();
List<String> outerJoin = new ArrayList<String>();
List<String> leftOuterJoin = new ArrayList<String>();
List<String> rightOuterJoin = new ArrayList<String>();
List<String> where = new ArrayList<String>();
List<String> having = new ArrayList<String>();
List<String> groupBy = new ArrayList<String>();
List<String> orderBy = new ArrayList<String>();
List<String> lastList = new ArrayList<String>();
List<String> columns = new ArrayList<String>();
List<String> values = new ArrayList<String>();
// 是否包含distinct关键字
boolean distinct;
public SQLStatement() {
// Prevent Synthetic Access
}
// .........
}
接下来以SELECT()
方法来追踪代码实现:
public T SELECT(String columns) {
// 设置语句类型为 SELECT
sql().statementType = SQLStatement.StatementType.SELECT;
// 然后添加进select列表即可。
sql().select.add(columns);
// 返回当前实例
return getSelf();
}
private SQLStatement sql() {
return sql;
}
其他应该也是类似的逻辑,再查看toString()方法即可。
public String toString() {
StringBuilder sb = new StringBuilder();
// 调用SQLStatement对象的sql()方法生成SQL语句
sql().sql(sb);
return sb.toString();
}
public String sql(Appendable a) {
// 就是StringBuilder 的 父接口
SafeAppendable builder = new SafeAppendable(a);
if (statementType == null) {
return null;
}
String answer;
// 根据类型判断调用哪段逻辑
switch (statementType) {
// ...
case SELECT:
answer = selectSQL(builder);
break;
// ...
}
return answer;
}
private String selectSQL(SafeAppendable builder) {
// 是否包含distinct关键字,
if (distinct) {
sqlClause(builder, "SELECT DISTINCT", select, "", "", ", ");
} else {
sqlClause(builder, "SELECT", select, "", "", ", ");
}
sqlClause(builder, "FROM", tables, "", "", ", ");
// joins 里面也是 批量的sqlClause调用
joins(builder);
sqlClause(builder, "WHERE", where, "(", ")", " AND ");
sqlClause(builder, "GROUP BY", groupBy, "", "", ", ");
sqlClause(builder, "HAVING", having, "(", ")", " AND ");
sqlClause(builder, "ORDER BY", orderBy, "", "", ", ");
return builder.toString();
}
// 核心的字符串拼接方法 sqlClause()
private void sqlClause(SafeAppendable builder, String keyword, List<String> parts, String open, String close,
String conjunction) {
if (!parts.isEmpty()) {
if (!builder.isEmpty()) {
builder.append("\n");
}
// 拼接SQL关键字
builder.append(keyword);
builder.append(" ");
// 拼接关键字后开始字符
builder.append(open);
String last = "________";
for (int i = 0, n = parts.size(); i < n; i++) {
String part = parts.get(i);
// 如果SQL关键字对应的子句内容不为OR或AND,则追加连接关键字
if (i > 0 && !part.equals(AND) && !part.equals(OR) && !last.equals(AND) && !last.equals(OR)) {
builder.append(conjunction);
}
// 追加子句内容
builder.append(part);
last = part;
}
// 追加关键字后结束字符
builder.append(close);
}
}
分析之后可以发现,join生成的语句不能保证和调用的顺序一致。这是这个类的一个缺点。
@Test
public void testSelectSql() {
String insertSql = new SQL() {{
SELECT("P.ID, P.USERNAME, P.PASSWORD");
SELECT("P.FIRST_NAME, P.LAST_NAME");
FROM("PERSON P");
FROM("USER1 U1");
FROM("USER2 U2");
INNER_JOIN("P.id=U1.id");
JOIN("P.id=U1.id");
ORDER_BY("P.LAST_NAME");
}}.toString();
System.out.println(insertSql);
}
/*
SELECT P.ID, P.USERNAME, P.PASSWORD, P.FIRST_NAME, P.LAST_NAME
FROM PERSON P, USER1 U1, USER2 U2
JOIN P.id=U1.id
INNER JOIN P.id=U1.id
ORDER BY P.LAST_NAME
*/
ScriptRunner #
作用:用于读取脚本文件中的SQL并执行。
public void testScriptRunner() {
try {
Connection connection = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis",
"sa", "");
ScriptRunner scriptRunner = new ScriptRunner(connection);
scriptRunner.runScript(Resources.getResourceAsReader("create-table.sql"));
} catch (Exception e) {
e.printStackTrace();
}
}
源码分析 #
public class ScriptRunner {
// 语句分隔符
private static final String DEFAULT_DELIMITER = ";";
// 持有连接
private final Connection connection;
// SQL异常是否中断执行
private boolean stopOnError;
// 是否抛出警告
private boolean throwWarning;
private boolean autoCommit;
private boolean sendFullScript;
// 。。。。
public ScriptRunner(Connection connection) {
this.connection = connection;
}
public void runScript(Reader reader) {
// 设置事务是否自动提交
setAutoCommit();
try {
// 是否一次性批量执行脚本文件中的所有SQL语句
if (sendFullScript) {
// 调用executeFullScript()方法一次性执行脚本文件中的所有SQL语句
executeFullScript(reader);
} else {
// 调用executeLineByLine()方法逐条执行脚本中的SQL语句,默认是走这
executeLineByLine(reader);
}
} finally {
rollbackConnection();
}
}
private void executeLineByLine(Reader reader) {
StringBuilder command = new StringBuilder();
try {
BufferedReader lineReader = new BufferedReader(reader);
String line;
while ((line = lineReader.readLine()) != null) {
// 读一行处理一行
handleLine(command, line);
}
commitConnection();
checkForMissingLineTerminator(command);
} catch (Exception e) {
String message = "Error executing: " + command + ". Cause: " + e;
printlnError(message);
throw new RuntimeSqlException(message, e);
}
}
private void handleLine(StringBuilder command, String line) throws SQLException {
String trimmedLine = line.trim();
// 判断是否是注释
if (lineIsComment(trimmedLine)) {
Matcher matcher = DELIMITER_PATTERN.matcher(trimmedLine);
if (matcher.find()) {
delimiter = matcher.group(5);
}
println(trimmedLine);
// 判断本行是否包含分号
} else if (commandReadyToExecute(trimmedLine)) {
command.append(line.substring(0, line.lastIndexOf(delimiter)));
command.append(LINE_SEPARATOR);
println(command);
// 调用Statement执行
executeStatement(command.toString());
// 执行完清空,下一行准备读取
command.setLength(0);
} else if (trimmedLine.length() > 0) {
command.append(line);
command.append(LINE_SEPARATOR);
}
}
}
SqlRunner #
该类对JDBC做了很好的封装,结合SQL工具类,能够很方便地通过Java代码执行SQL语句并检索SQL执行结果。SqlRunner类提供了几个操作数据库的方法,分别说明如下。
源码分析 #
@Test
public void testSelectOne() throws SQLException {
SqlRunner sqlRunner = new SqlRunner(connection);
String qryUserSql = new SQL() {{
SELECT("*");
FROM("user");
WHERE("id = ?");
}}.toString();
Map<String, Object> resultMap = sqlRunner.selectOne(qryUserSql, Integer.valueOf(1));
System.out.println(JSON.toJSONString(resultMap));
}
public Map<String, Object> selectOne(String sql, Object... args) throws SQLException {
// 实际调用的是 selectAll方法,查询所有再返回第一条,,,,,,
List<Map<String, Object>> results = selectAll(sql, args);
if (results.size() != 1) {
throw new SQLException("Statement returned " + results.size() + " results where exactly one (1) was expected.");
}
return results.get(0);
}
public List<Map<String, Object>> selectAll(String sql, Object... args) throws SQLException {
// 直接就是调用 PreparedStatement 的 setParameters方法罢了。
PreparedStatement ps = connection.prepareStatement(sql);
try {
setParameters(ps, args);
ResultSet rs = ps.executeQuery();
return getResults(rs);
} finally {
try {
ps.close();
} catch (SQLException e) {
//ignore
}
}
}
MetaObject #
MetaObject是MyBatis中的反射工具类,该工具类在MyBatis源码中出现的频率非常高。使用MetaObject工具类,我们可以很优雅地获取和设置对象的属性值。
public class MetaObjectExample {
@Data
@AllArgsConstructor
private static class User {
List<Order> orders;
String name;
Integer age;
}
@Data
@AllArgsConstructor
private static class Order {
String orderNo;
String goodsName;
}
@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属性且orderNo属性有对应的name方法:" + metaObject.hasGetter("name"));
}
}
/*
《Mybatis源码深度解析》图书
《AngularJS入门与进阶》图书
是否有orderNo属性且orderNo属性有对应的Getter方法:false
是否有name属性且orderNo属性有对应的name方法:true
*/
MetaClass #
MetaClass是MyBatis中的反射工具类,与MetaOjbect不同的是,MetaObject用于获取和设置对象的属性值,而MetaClass则用于获取类相关的信息。例如,我们可以使用MetaClass判断某个类是否有默认构造方法,还可以判断类的属性是否有对应的Getter/Setter方法。接下来我们看一下MetaClass工具类的使用,代码如下:
public class MetaClassExample {
@Data
@AllArgsConstructor
private static class Order {
String orderNo;
String goodsName;
}
@Test
public void testMetaClass() {
MetaClass metaClass = MetaClass.forClass(Order.class, new DefaultReflectorFactory());
// 获取所有有Getter方法的属性名
String[] getterNames = metaClass.getGetterNames();
System.out.println(JSON.toJSONString(getterNames));
// 是否有默认构造方法
System.out.println("是否有默认构造方法:" + metaClass.hasDefaultConstructor());
// 某属性是否有对应的Getter/Setter方法
System.out.println("orderNo属性是否有对应的Getter方法:" + metaClass.hasGetter("orderNo"));
System.out.println("orderNo属性是否有对应的Setter方法:" + metaClass.hasSetter("orderNo"));
System.out.println("orderNo属性类型:" + metaClass.getGetterType("orderNo"));
// 获取属性Getter方法
Invoker invoker = metaClass.getGetInvoker("orderNo");
try {
// 通过Invoker对象调用Getter方法获取属性值
Object orderNo = invoker.invoke(new Order("order20171024010248","《Mybatis源码深度解析》图书"), null);
System.out.println(orderNo);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
/*
["orderNo","goodsName"]
是否有默认构造方法:false
orderNo属性是否有对应的Getter方法:true
orderNo属性是否有对应的Setter方法:true
orderNo属性类型:class java.lang.String
order20171024010248
*/
ObjectFactory #
ObjectFactory是MyBatis中的对象工厂,MyBatis每次创建Mapper映射结果对象的新实例时,都会使用一个对象工厂(ObjectFactory)实例来完成。ObjectFactory接口只有一个默认的实现,即DefaultObjectFactory,默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。
@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, 2, 3));
map.put("test", "test");
System.out.println(list);
System.out.println(map);
}
MyBatis中使用ObjectFactory实例创建Mapper映射结果对象的目的是什么呢?
这是MyBatis提供的一种扩展机制。有些情况下,在得到映射结果之前我们需要处理一些逻辑,或者在执行该类的有参构造方法时,在传入参数之前,要对参数进行一些处理,这时我们可以通过自定义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完成后,还需要在MyBatis主配置文件中通过<objectFactory>标签配置自定义的ObjectFactory,具体如下:
<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中定义的拦截逻辑,然后执行一次额外的查询。
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());
}
}
/**
class com.blog4java.mybatis.ProxyFactoryExample$Order_$$_jvstc25_0
《Mybatis源码深度解析》图书
**/