## SQL 類 MyBatis 提供了一個 SQL 工具類,使用這個工具類,我們可以很方便在 Java 代碼動態構建 SQL 語句 ```java String newSql = new SQL() ({ SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL N ...
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());
}
}