1、使用SQL类生成语句
SQL工具类中提供的所有方法及作用:
// 开始一个SELECT子句或将内容追加到SELECT子句。
// 方法可以被多次调用,参数也会添加到SELECT子句。
// 参数通常是使用逗号分隔的列名或列的别名列表,也可以是数据库驱动程序接收的任意关键字。
T SELECT(String columns);
T SELECT(String... columns);
// 开始一个SELECT子句或将内容追加到SELECT子句。
// 同时可以插入DISTINCT关键字到SELECT语句中。
// 方法可以被多次调用,参数也会添加到SELECT子句。
// 参数通常使用逗号分隔的列名或者列的别名列表,也可以是数据库驱动程序接收的任意关键字。
T SELECT_DISTINCT(String columns);
T SELECT_DISTINCT(String... columns);
// 开始或插入FROM子句。
// 方法可以被多次调用,参数会添加到FROM子句。
// 参数通常是表名或列名,也可以是数据库驱动程序接收的任意关键字。
T FROM(String table);
T FROM(String... tables);
// 根据不同的方法添加对应类型的JOIN子句。
// 例如INNER_JOIN()方法添加INNER_JOIN子句,LEFT_OUTER_JOIN()方法添加LEFT JOIN子句。
// 参数可以包含由列名和JOIN ON 条件组合成的标准JOIN。
T JOIN(String join);
T JOIN(String... joins);
T INNER_JOIN(String join);
T INNER_JOIN(String... joins);
T LEFT_OUTER_JOIN(String join);
T LEFT_OUTER_JOIN(String... joins);
T RIGHT_OUTER_JOIN(String join);
T RIGHT_OUTER_JOIN(String... joins);
// 插入新的WHERE子句条件,并通过AND关键字连接。
// 方法可以多次被调用,每次都由AND来连接新条件。
// 使用OR()方法可以追加OR关键字。
T WHERE(String conditions);
T WHERE(String... conditions);
// 使用OR()来分隔当前的WHERE子句条件。
// 可以被多次调用,但在一行中多次调用可能生成错误的SQL语句。
T OR();
// 使用AND来分隔当前的WHERE子句条件。
// 可以被多次调用,但在一行中多次调用可能会生成错误的SQL语句。
// 这个方法使用的比较少,因为WHERE()和HAVING()方法都会自动追加AND,只有必要时才会额外调用AND()方法。
T AND();
// 插入新的GROUP BY子句,通过逗号连接。
// 方法可以被多次调用,每次都会使用逗号连接新的条件。
T GROUP_BY(String columns);
T GROUP_BY(String... columns);
// 插入新的HAVING子句条件。由AND关键字连接。
// 方法可以被多次调用,每次都是由AND来连接新的条件。
T HAVING(String conditions);
T HAVING(String... conditions);
// 插入新的ORDER BY子句元素,由逗号连接。
// 可以被多次调用,每次都由逗号连接新的条件。
T ORDER_BY(String columns);
T ORDER_BY(String... columns);
// 开始一个DELETE语句并指定表名。通常它后面都会跟着WHERE语句。
T DELETE_FROM(String table);
// 开始一个INSERT语句并指定表名,后面都会跟着一个或者多个VALUES(),或者INTO_COLUMNS()和INTO_VALUES()
T INSERT_INTO(String tableName);
// 针对UPDATE语句,插入SET子句中。
T SET(String sets);
T SET(String... sets);
// 开始一个UPDATE语句并指定需要更新的表名。后面都会跟着一个或者多个SET()方法,通常会有一个或多个WHERE()方法。
T UPDATE(String table);
// 插入INSERT语句中,第一个参数是要插入的列名,第二个参数则是该列的值。
T VALUES(String columns, String values);
// 追加字段到INSERT子句中,该方法必须和INTOVALUES()联合使用。
T INTO_COLUMNS(String... columns);
// 追加字段值到INSERT子句中,该方法必须和INTO_COLUMNS()方法联合使用。
T INTO_VALUES(String... values);
SQL继承自AbstractSQL类,只重写了该类的getSelf()方法。
public class SQL extends AbstractSQL<SQL> {
@Override
public SQL getSelf() {
return this;
}
}
举个例子;
SQL sql = new SQL();
sql.SELECT("u.id, u.name, u.password, u.phone, u.nick_name");
sql.FROM("user u");
sql.WHERE("u.name = #{name}");
sql.WHERE("u.password = #{password}");
sql.getSelf().toString();
所有功能由AbstractSQL类完成,AbstractSQL类中维护了一个SQLStatement内部类的实例和一系列构造SQL语句的方法。
private static final String AND = ") \nAND (";
private static final String OR = ") \nOR (";
// 维护了SQLStatement静态内部类实例。
private final SQLStatement sql = new SQLStatement();
具体的SQL拼装操作则是在SQL的静态内部类中完成的。
// 定义了增删改查操作类型
public enum StatementType {
DELETE, INSERT, SELECT, UPDATE
}
// 定义本次操作对应的类型。
StatementType statementType;
// 以下列表是用来存放关键字后面的字符串。
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>();
boolean distinct;
SQL中提供的API,比如SELECT(String columns)等,是提供给开发者向SQLStatement中的相对应的字符串容器插入相关数据的。具体的拼装操作则是由如下开始的:
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sql().sql(sb);
return sb.toString();
}
public String sql(Appendable a) {
// 用于拼装字符串
SafeAppendable builder = new SafeAppendable(a);
if (statementType == null) {
return null;
}
String answer;
switch (statementType) {
// 拼接DELETE操作
case DELETE:
answer = deleteSQL(builder);
break;
// 拼接INSERT操作
case INSERT:
answer = insertSQL(builder);
break;
// 拼接SELECT操作
case SELECT:
answer = selectSQL(builder);
break;
// 拼接UPDATE操作
case UPDATE:
answer = updateSQL(builder);
break;
default:
answer = null;
}
return answer;
}
delete操作:
private String deleteSQL(SafeAppendable builder) {
sqlClause(builder, "DELETE FROM", tables, "", "", "");
sqlClause(builder, "WHERE", where, "(", ")", " AND ");
return builder.toString();
}
insert操作:
private String insertSQL(SafeAppendable builder) {
sqlClause(builder, "INSERT INTO", tables, "", "", "");
sqlClause(builder, "", columns, "(", ")", ", ");
sqlClause(builder, "VALUES", values, "(", ")", ", ");
return builder.toString();
}
select操作:
private String selectSQL(SafeAppendable builder) {
// 拼接SELECT关键字内容
if (distinct) {
sqlClause(builder, "SELECT DISTINCT", select, "", "", ", ");
} else {
sqlClause(builder, "SELECT", select, "", "", ", ");
}
// 拼接FROM关键字内容
sqlClause(builder, "FROM", tables, "", "", ", ");
// 拼接join关键字内容
joins(builder);
// 拼接WHERE关键字内容
sqlClause(builder, "WHERE", where, "(", ")", " AND ");
// 拼接GROUP BY关键字内容
sqlClause(builder, "GROUP BY", groupBy, "", "", ", ");
// 拼接HAVING关键字内容
sqlClause(builder, "HAVING", having, "(", ")", " AND ");
// 拼接ORDER BY关键字内容
sqlClause(builder, "ORDER BY", orderBy, "", "", ", ");
return builder.toString();
}
private void joins(SafeAppendable builder) {
sqlClause(builder, "JOIN", join, "", "", "\nJOIN ");
sqlClause(builder, "INNER JOIN", innerJoin, "", "", "\nINNER JOIN ");
sqlClause(builder, "OUTER JOIN", outerJoin, "", "", "\nOUTER JOIN ");
sqlClause(builder, "LEFT OUTER JOIN", leftOuterJoin, "", "", "\nLEFT OUTER JOIN ");
sqlClause(builder, "RIGHT OUTER JOIN", rightOuterJoin, "", "", "\nRIGHT OUTER JOIN ");
}
update操作:
private String updateSQL(SafeAppendable builder) {
sqlClause(builder, "UPDATE", tables, "", "", "");
joins(builder);
sqlClause(builder, "SET", sets, "", "", ", ");
sqlClause(builder, "WHERE", where, "(", ")", " AND ");
limitingRowsStrategy.appendClause(builder, null, limit);
return builder.toString();
}
最后所有的SQL拼接操作实际上都是依赖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");
}
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);
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);
}
}
2、使用ScriptRunner执行脚本
ScriptRunner工具类用于读取脚本文件中的SQL语句并执行。
举个例子:
// 创建DataSourceFactory实例。
DataSourceFactory dataSourceFactory = new UnpooledDataSourceFactory();
// 创建Properties实例。
Properties properties = new Properties();
// 读取类路径下的database.properties文件内容。
InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("database.properties");
properties.load(inputStream);
// 将文件属性内容设置到DataSourceFactory中。
dataSourceFactory.setProperties(properties);
// 通过工厂模式获取DataSource实例。
DataSource dataSource = dataSourceFactory.getDataSource();
Connection connection = dataSource.getConnection();
// 创建ScriptRunner实例。
ScriptRunner runner = new ScriptRunner(connection);
// 执行脚本。
runner.runScript(Resources.getResourceAsReader("run.sql"));
ScriptRunner工具类的构造方法需要一个java.sql.Connection对象作为参数,创建ScriptRunner对象后,调用该对象的runScript()方法即可,该方法接收一个读取SQL脚本文件的Reader对象作为参数。
// 异常是否中断程序执行。
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 errorLogWriter = new PrintWriter(System.err);
// 脚本文件中SQL语句的分隔符,默认为分号。
private String delimiter = DEFAULT_DELIMITER;
// 是否支持SQL语句分隔符,单独占一行。
private boolean fullLineDelimiter;
runScript方法代码如下:
public void runScript(Reader reader) {
// 设置自动提交。
setAutoCommit();
try {
// 设置脚本是否是逐条执行,默认情况下以分号分隔。
if (sendFullScript) {
// 调用executeFullScript()方法一次性执行脚本文件中的所有SQL语句。
executeFullScript(reader);
} else {
// 调用executeLineByLine()方法逐条执行脚本中的SQL语句。
executeLineByLine(reader);
}
} finally {
rollbackConnection();
}
}
如上代码所示:
ScriptRunner类的runScript()方法具体做了以下几件事情:
(1)、调用setAutoCommit()方法,根据autoCommit属性的值设置事务是否自动提交。
(2)、判断sendFullScript属性值,如果值为true,则调用executeFullScript()方法一次性读取SQL脚本中的所有内容,然后调用JDBC中的Statement对象的execute()方法一次性执行脚本中的所有SQL语句。
(3)、如果sendFullScript属性为false,则调用executeLineByLine()方法逐行读取SQL脚本文件,以分号作为一条SQL语句结束的标志,逐条执行SQL语句。
setAutoCommit()方法如下:
private void setAutoCommit() {
try {
if (autoCommit != connection.getAutoCommit()) {
connection.setAutoCommit(autoCommit);
}
} catch (Throwable t) {
throw new RuntimeSqlException("Could not set AutoCommit to " + autoCommit + ". Cause: " + t, t);
}
}
executeLineByLine()方法如下:
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();
// 判断该行是否是SQL注释。
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);
// 执行该条完整的SQL语句。
executeStatement(command.toString());
command.setLength(0);
// 该行中不包含分号,说明这条SQL语句未结束,追加本行内容到之前读取的内容中。
} else if (trimmedLine.length() > 0) {
command.append(line);
command.append(LINE_SEPARATOR);
}
}
3、使用SqlRunner操作数据库
Mybatis中提供了一个非常实用的、用于操作数据库的SqlRunner工具类,该类对JDBC做了很好的封装,结合SQL工具类,能够很方便地通过Java代码执行SQL语句并检索SQL执行结果。
SQLRunner类提供了几个操作数据库的方法,分别说明如下:
(1)、closeConnection方法:用于关闭Connection对象
public void closeConnection() {
try {
connection.close();
} catch (SQLException e) {
//ignore
}
}
(2)selectOne(String sql, Object... args) :执行SELECT语句,SQL语句中可以使用占位符,如果SQL中包含占位符,则可变参数用于为参数占位符赋值,该方法只返回一条记录,若查询结果行数不等于一,则会抛出SQLException异常。
/*
* Executes a SELECT statement that returns one row.
*
* @param sql The SQL
* @param args The arguments to be set on the statement.
* @return The row expected.
* @throws SQLException If less or more than one row is returned
*/
public Map<String, Object> selectOne(String sql, Object... args) throws SQLException {
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);
}
(3)、selectAll(String sql, Object... args):该方法和selectOne()方法作用相同,只不过该方法可以返回多条记录,方法返回值是一个List对象,List中包含多个Map对象,每个Map对象对应数据库中的一行记录。
/*
* Executes a SELECT statement that returns multiple rows.
*
* @param sql The SQL
* @param args The arguments to be set on the statement.
* @return The list of rows expected.
* @throws SQLException If statement preparation or execution fails
*/
public List<Map<String, Object>> selectAll(String sql, Object... args) throws SQLException {
PreparedStatement ps = connection.prepareStatement(sql);
try {
// 调用setParameters为SQL中的参数占位符赋值。
setParameters(ps, args);
// 调用PreparedStatement的executeQuery()方法执行查询操作。
ResultSet rs = ps.executeQuery();
// 将查询结果转换为List。
return getResults(rs);
} finally {
try {
ps.close();
} catch (SQLException e) {
//ignore
}
}
}
(4)、insert(String sql, Object... args):执行一条INSERT语句,插入一条记录。
/*
* Executes an INSERT statement.
*
* @param sql The SQL
* @param args The arguments to be set on the statement.
* @return The number of rows impacted or BATCHED_RESULTS if the statements are being batched.
* @throws SQLException If statement preparation or execution fails
*/
public int insert(String sql, Object... args) throws SQLException {
PreparedStatement ps;
if (useGeneratedKeySupport) {
ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
} else {
ps = connection.prepareStatement(sql);
}
try {
setParameters(ps, args);
ps.executeUpdate();
if (useGeneratedKeySupport) {
List<Map<String, Object>> keys = getResults(ps.getGeneratedKeys());
if (keys.size() == 1) {
Map<String, Object> key = keys.get(0);
Iterator<Object> i = key.values().iterator();
if (i.hasNext()) {
Object genkey = i.next();
if (genkey != null) {
try {
return Integer.parseInt(genkey.toString());
} catch (NumberFormatException e) {
//ignore, no numeric key support
}
}
}
}
}
return NO_GENERATED_KEY;
} finally {
try {
ps.close();
} catch (SQLException e) {
//ignore
}
}
}
(5)、update(String sql, Object... args):更新若干条记录。
/*
* Executes an UPDATE statement.
*
* @param sql The SQL
* @param args The arguments to be set on the statement.
* @return The number of rows impacted or BATCHED_RESULTS if the statements are being batched.
* @throws SQLException If statement preparation or execution fails
*/
public int update(String sql, Object... args) throws SQLException {
PreparedStatement ps = connection.prepareStatement(sql);
try {
setParameters(ps, args);
return ps.executeUpdate();
} finally {
try {
ps.close();
} catch (SQLException e) {
//ignore
}
}
}
(6)、delete(String sql, Object... args):删除若干条记录。
/*
* Executes a DELETE statement.
*
* @param sql The SQL
* @param args The arguments to be set on the statement.
* @return The number of rows impacted or BATCHED_RESULTS if the statements are being batched.
* @throws SQLException If statement preparation or execution fails
*/
public int delete(String sql, Object... args) throws SQLException {
return update(sql, args);
}
(7)、void run(String sql):执行任意一条SQL语句,最好为DDL语句。
/*
* Executes any string as a JDBC Statement.
* Good for DDL
*
* @param sql The SQL
* @throws SQLException If statement preparation or execution fails
*/
public void run(String sql) throws SQLException {
Statement stmt = connection.createStatement();
try {
stmt.execute(sql);
} finally {
try {
stmt.close();
} catch (SQLException e) {
//ignore
}
}
}
举个例子:
// 创建DataSourceFactory实例。
DataSourceFactory dataSourceFactory = new UnpooledDataSourceFactory();
// 创建Properties实例。
Properties properties = new Properties();
// 读取类路径下的database.properties文件内容。
InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("database.properties");
properties.load(inputStream);
// 将文件属性内容设置到DataSourceFactory中。
dataSourceFactory.setProperties(properties);
// 通过工厂模式获取DataSource实例。
DataSource dataSource = dataSourceFactory.getDataSource();
SQL sql = new SQL(){
{
SELECT("id, name, password, phone, nick_name, create_time");
FROM("user");
WHERE("id = ?");
}
};
SqlRunner sqlRunner = new SqlRunner(dataSource.getConnection());
Map<String, Object> map = sqlRunner.selectOne(sql.toString(), Integer.valueOf(1));
4、MetaObject详解
MetaObject是Mybatis中的反射工具类,该工具在Mybatis源码中出现的频率非常高。使用MetaObject工具类,可以获取和设置对象的属性。
举个例子:
List<Order> orders = new ArrayList<Order>() {
{
add(new Order("order11111", "book1"));
add(new Order("order22222", "book2"));
}
};
User user = new User(orders, "gaoming", 23);
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].orderNumber", "order33333");
System.out.println(metaObject.hasGetter("orderNumber"));
System.out.println(metaObject.hasGetter("name"));
5、MetaClass详解
MetaClass是Mybatis中的反射工具类,与MetaObject不同的是,MetaObject用于获取和设置对象的属性值,而MetaClass则用于获取类相关的信息。
举个例子:
MetaClass metaClass = MetaClass.forClass(Order.class, new DefaultReflectorFactory());
String[] getterNames = metaClass.getGetterNames();
System.out.println(getterNames);
System.out.println(metaClass.hasDefaultConstructor());
System.out.println(metaClass.hasGetter("orderNumber"));
System.out.println(metaClass.hasSetter("orderNumber"));
System.out.println(metaClass.getGetterType("orderNumber"));
Invoker invoker = metaClass.getGetInvoker("orderNumber");
try {
Object object = invoker.invoke(new Order("order1111", "book1"), null);
System.out.println(object);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
6、ObjectFactory详解
ObjectFactory是Mybatis中的对象工厂,Mybatis每次创建Mapper映射结果对象的新实例时,都会使用一个对象工厂(ObjectFactory)实例来完成。ObjectFactory接口只有一个默认的实现,即DefaultObjectFactory,默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。
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);
需要注意的是,DefaultObjectFactory实现类支持通过接口的方式创建对象,例如当指定创建java.util.List实例时,实际上创建的是java.util.ArrayList对象。List、Map、Set接口对应的实现分别为ArrayList、HashMap、HashSet。如果需要扩展,直接继承DefaultObjectFactory即可。
7、ProxyFactory详解
ProxyFactory是Mybatis中的代理工厂,主要用于创建动态代理对象,ProxyFactory接口有两个不同的实现,分别为CglibProxyFactory和JavassistProxyFactory。从实现类的名称可以看出,Mybatis支持两种动态代理策略,分别为Cglib和Javassist动态代理。ProxyFactory主要用于实现Mybatis的懒加载功能。当开启懒加载后,Mybatis创建Mapper映射结果对象后,会通过ProxyFactory映射结果对象的代理对象。当调用代理对象的Getter方法获取数据时,会执行CglibProxyFactory或JavassistProxyFactory中定义的拦截逻辑,然后执行一次额外的查询。
ProxyFactory proxyFactory = new JavassistProxyFactory();
Order order = new Order("book1", "order111");
ObjectFactory objectFactory = new DefaultObjectFactory();
Object proxyOrder = proxyFactory.createProxy(order,
objectFactory.create(ResultLoaderMap.class),
objectFactory.create(Configuration.class),
objectFactory,
(List)Arrays.asList(String.class, String.class),
(List)Arrays.asList(order.getOrderName(), order.getOrderNumber()));
System.out.println(proxyOrder.getClass());
System.out.println(((Order)proxyOrder).getOrderName());