国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - 淺談MyBatis通用Mapper實現原理

淺談MyBatis通用Mapper實現原理

2021-06-06 13:57isea533 Java教程

這篇文章主要介紹了淺談MyBatis通用Mapper實現原理,本文會先介紹通用 Mapper 的簡單原理,然后使用最簡單的代碼來實現這個過程。感興趣的小伙伴們可以參考一下

本文會先介紹通用 mapper 的簡單原理,然后使用最簡單的代碼來實現這個過程。

基本原理

通用 mapper 提供了一些通用的方法,這些通用方法是以接口的形式提供的,例如。

?
1
2
3
4
5
6
7
public interface selectmapper<t> {
  /**
   * 根據實體中的屬性值進行查詢,查詢條件使用等號
   */
  @selectprovider(type = baseselectprovider.class, method = "dynamicsql")
  list<t> select(t record);
}

接口和方法都使用了泛型,使用該通用方法的接口需要指定泛型的類型。通過 java 反射可以很容易得到接口泛型的類型信息,代碼如下。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
type[] types = mapperclass.getgenericinterfaces();
class<?> entityclass = null;
for (type type : types) {
  if (type instanceof parameterizedtype) {
    parameterizedtype t = (parameterizedtype) type;
    //判斷父接口是否為 selectmapper.class
    if (t.getrawtype() == selectmapper.class) {
      //得到泛型類型
      entityclass = (class<?>) t.getactualtypearguments()[0];
      break;
    }
  }
}

實體類中添加的 jpa 注解只是一種映射實體和數據庫表關系的手段,通過一些默認規則或者自定義注解也很容易設置這種關系,獲取實體和表的對應關系后,就可以根據通用接口方法定義的功能來生成和 xml 中一樣的 sql 代碼。動態生成 xml 樣式代碼的方式有很多,最簡單的方式就是純 java 代碼拼字符串,通用 mapper 為了盡可能的少的依賴選擇了這種方式。如果使用模板(如 freemarker,velocity 和 beetl 等模板引擎)實現,自由度會更高,也能方便開發人員調整。

在 mybatis 中,每一個方法(注解或 xml 方式)經過處理后,最終會構造成 mappedstatement 實例,這個對象包含了方法id(namespace+id)、結果映射、緩存配置、sqlsource 等信息,和 sql 關系最緊密的是其中的 sqlsource,mybatis 最終執行的 sql 時就是通過這個接口的 getboundsql 方法獲取的。

在 mybatis 中,使用@selectprovider 這種方式定義的方法,最終會構造成 providersqlsource,providersqlsource 是一種處于中間的 sqlsource,它本身不能作為最終執行時使用的 sqlsource,但是他會根據指定方法返回的 sql 去構造一個可用于最后執行的 staticsqlsource,staticsqlsource的特點就是靜態 sql,支持在 sql 中使用#{param} 方式的參數,但是不支持 <if>,<where> 等標簽。

為了能根據實體類動態生成支持動態 sql 的方法,通用 mapper 從這里入手,利用providersqlsource 可以生成正常的 mappedstatement,可以直接利用 mybatis 各種配置和命名空間的特點(這是通用 mapper 選擇這種方式的主要原因)。在生成 mappedstatement 后,“過河拆橋” 般的利用完就把 providersqlsource 替換掉了,正常情況下,providersqlsource 根本就沒有執行的機會。在通用 mapper 定義的實現方法中,提供了 mappedstatement 作為參數,有了這個參數,我們就可以根據 ms 的 id(規范情況下是 接口名.方法名)得到接口,通過接口的泛型可以獲取實體類(entityclass),根據實體和表的關系我們可以拼出 xml 方式的動態 sql,一個簡單的方法如下。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * 查詢全部結果
 *
 * @param ms
 * @return
 */
public string selectall(mappedstatement ms) {
  final class<?> entityclass = getentityclass(ms);
  //修改返回值類型為實體類型
  setresulttype(ms, entityclass);
  stringbuilder sql = new stringbuilder();
  sql.append(sqlhelper.selectallcolumns(entityclass));
  sql.append(sqlhelper.fromtable(entityclass, tablename(entityclass)));
  sql.append(sqlhelper.orderbydefault(entityclass));
  return sql.tostring();
}

拼出的 xml 形式的動態 sql,使用 mybatis 的 xmllanguagedriver 中的 createsqlsource 方法可以生成 sqlsource。然后使用反射用新的 sqlsource 替換providersqlsource 即可,如下代碼。

?
1
2
3
4
5
6
7
8
9
10
/**
 * 重新設置sqlsource
 *
 * @param ms
 * @param sqlsource
 */
protected void setsqlsource(mappedstatement ms, sqlsource sqlsource) {
  metaobject msobject = systemmetaobject.forobject(ms);
  msobject.setvalue("sqlsource", sqlsource);
}

metaobject 是mybatis 中很有用的工具類,mybatis 的結果映射就是靠這種方式實現的。反射信息使用的 defaultreflectorfactory,這個類會緩存反射信息,因此 mybatis 的結果映射的效率很高。

到這里核心的內容都已經說完了,雖然知道怎么去替換 sqlsource了,但是!什么時候去替換呢?

這一直都是一個難題,如果不大量重寫 mybatis 的代碼很難萬無一失的完成這個任務。通用 mapper 并沒有去大量重寫,主要是考慮到以后的升級,也因此在某些特殊情況下,通用 mapper 的方法會在沒有被替換的情況下被調用,這個問題在將來的 mybatis 3.5.x 版本中會以更友好的方式解決(目前的 providersqlsource 已經比以前能實現更多的東西,后面會講)。

針對不同的運行環境,需要用不同的方式去替換。當使用純 mybatis (沒有spring)方式運行時,替換很簡單,因為會在系統中初始化 sqlsessionfactory,可以初始化的時候進行替換,這個時候也不會出現前面提到的問題。替換的方式也很簡單,通過 sqlsessionfactory 可以得到 sqlsession,然后就能得到 configuration,通過 configuration.getmappedstatements() 就能得到所有的 mappedstatement,循環判斷其中的方法是否為通用接口提供的方法,如果是就按照前面的方式替換就可以了。

在使用 spring 的情況下,以繼承的方式重寫了 mapperscannerconfigurer 和 mapperfactorybean,在 spring 調用 checkdaoconfig 的時候對 sqlsource 進行替換。在使用 spring boot 時,提供的 mapper-starter 中,直接注入 list<sqlsessionfactory> sqlsessionfactorylist 進行替換。

下面我們按照這個思路,以最簡練的代碼,實現一個通用方法。

實現一個簡單的通用 mapper

1. 定義通用接口方法

?
1
2
3
4
public interface basemapper<t> {
  @selectprovider(type = selectmethodprovider.class, method = "select")
  list<t> select(t entity);
}

這里定義了一個簡單的 select 方法,這個方法判斷參數中的屬性是否為空,不為空的字段會作為查詢條件進行查詢,下面是對應的 provider。

?
1
2
3
4
5
public class selectmethodprovider {
  public string select(object params) {
    return "什么都不是!";
  }
}

這里的 provider 不會最終執行,只是為了在初始化時可以生成對應的 mappedstatement。

2. 替換 sqlsource

下面代碼為了簡單,都指定的 basemapper 接口,并且沒有特別的校驗。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class simplemapperhelper {
  public static final xmllanguagedriver xml_language_driver
      = new xmllanguagedriver();
  /**
   * 獲取泛型類型
   */
  public static class getentityclass(class<?> mapperclass){
    type[] types = mapperclass.getgenericinterfaces();
    class<?> entityclass = null;
    for (type type : types) {
      if (type instanceof parameterizedtype) {
        parameterizedtype t = (parameterizedtype) type;
        //判斷父接口是否為 basemapper.class
        if (t.getrawtype() == basemapper.class) {
          //得到泛型類型
          entityclass = (class<?>) t.getactualtypearguments()[0];
          break;
        }
      }
    }
    return entityclass;
  }
 
  /**
   * 替換 sqlsource
   */
  public static void changems(mappedstatement ms) throws exception {
    string msid = ms.getid();
    //標準msid為 包名.接口名.方法名
    int lastindex = msid.lastindexof(".");
    string methodname = msid.substring(lastindex + 1);
    string interfacename = msid.substring(0, lastindex);
    class<?> mapperclass = class.forname(interfacename);
    //判斷是否繼承了通用接口
    if(basemapper.class.isassignablefrom(mapperclass)){
      //判斷當前方法是否為通用 select 方法
      if (methodname.equals("select")) {
        class entityclass = getentityclass(mapperclass);
        //必須使用<script>標簽包裹代碼
        stringbuffer sqlbuilder = new stringbuffer("<script>");
        //簡單使用類名作為包名
        sqlbuilder.append("select * from ").append(entityclass.getsimplename());
        field[] fields = entityclass.getdeclaredfields();
        sqlbuilder.append(" <where> ");
        for (field field : fields) {
          sqlbuilder.append("<if test=\"")
              .append(field.getname()).append("!=null\">");
          //字段名直接作為列名
          sqlbuilder.append(" and ").append(field.getname())
               .append(" = #{").append(field.getname()).append("}");
          sqlbuilder.append("</if>");
        }
        sqlbuilder.append("</where>");
        sqlbuilder.append("</script>");
        //解析 sqlsource
        sqlsource sqlsource = xml_language_driver.createsqlsource(
            ms.getconfiguration(), sqlbuilder.tostring(), entityclass);
        //替換
        metaobject msobject = systemmetaobject.forobject(ms);
        msobject.setvalue("sqlsource", sqlsource);
      }
    }
  }
 
}

changems 方法簡單的從 msid 開始,獲取接口和實體信息,通過反射回去字段信息,使用 <if> 標簽動態判斷屬性值,這里的寫法和 xml 中一樣,使用 xmllanguagedriver 處理時需要在外面包上 <script> 標簽。生成 sqlsource 后,通過反射替換了原值。

3. 測試

針對上面代碼,提供一個 country 表和對應的各種類。

實體類。

?
1
2
3
4
5
6
public class country {
 private long  id;
 private string countryname;
 private string countrycode;
 //省略 getter,setter
}

mapper 接口。

?
1
2
3
public interface countrymapper extends basemapper<country> {
 
}

啟動 mybatis 的公共類。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class sqlsessionhelper {
  private static sqlsessionfactory sqlsessionfactory;
 
  static {
    try {
      reader reader = resources.getresourceasreader("mybatis-config.xml");
      sqlsessionfactory = new sqlsessionfactorybuilder().build(reader);
      reader.close();
      //創建數據庫
      sqlsession session = null;
      try {
        session = sqlsessionfactory.opensession();
        connection conn = session.getconnection();
        reader = resources.getresourceasreader("hsqldb.sql");
        scriptrunner runner = new scriptrunner(conn);
        runner.setlogwriter(null);
        runner.runscript(reader);
        reader.close();
      } finally {
        if (session != null) {
          session.close();
        }
      }
    } catch (ioexception ignore) {
      ignore.printstacktrace();
    }
  }
 
  public static sqlsession getsqlsession() {
    return sqlsessionfactory.opensession();
  }
 
}

 配置文件。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="utf-8" ?>
<!doctype configuration
  public "-//mybatis.org//dtd config 3.0//en"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
 <environments default="development">
  <environment id="development">
   <transactionmanager type="jdbc">
    <property name="" value=""/>
   </transactionmanager>
   <datasource type="unpooled">
    <property name="driver" value="org.hsqldb.jdbcdriver"/>
    <property name="url" value="jdbc:hsqldb:mem:basetest"/>
    <property name="username" value="sa"/>
   </datasource>
  </environment>
 </environments>
 
 <mappers>
  <package name="tk.mybatis.simple.mapper"/>
 </mappers>
 
</configuration>

初始化sql。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
drop table country if exists;
 
create table country (
 id integer,
 countryname varchar(32),
 countrycode varchar(2)
);
 
insert into country (id, countryname, countrycode) values(1,'angola','ao');
insert into country (id, countryname, countrycode) values(23,'botswana','bw');
-- 省略部分
insert into country (id, countryname, countrycode) values(34,'chile','cl');
insert into country (id, countryname, countrycode) values(35,'china','cn');
insert into country (id, countryname, countrycode) values(36,'colombia','co');

測試代碼。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class simpletest {
 
  public static void main(string[] args) throws exception {
    sqlsession sqlsession = sqlsessionhelper.getsqlsession();
    configuration configuration = sqlsession.getconfiguration();
    hashset<mappedstatement> mappedstatements
        = new hashset<mappedstatement>(configuration.getmappedstatements());
    //如果注釋下面替換步驟就會出錯
    for (mappedstatement ms : mappedstatements) {
      simplemapperhelper.changems(ms);
    }
    //替換后執行該方法
    countrymapper mapper = sqlsession.getmapper(countrymapper.class);
    country query = new country();
    //可以修改條件或者注釋條件查詢全部
    query.setcountrycode("cn");
    list<country> countrylist = mapper.select(query);
    for (country country : countrylist) {
      system.out.printf("%s - %s\n",
          country.getcountryname(),
          country.getcountrycode());
    }
    sqlsession.close();
  }
}

通過簡化版的處理過程應該可以和前面的內容聯系起來,從而理解通用 mapper 的簡單處理過程。

完整代碼下載:simple-mapper

最新的 providersqlsource

早期的 providersqlsource 有個缺點就是定義的方法要么沒有參數,要么只能是 object parameterobject 參數,這個參數最終的形式在開發時也不容易一次寫對,因為不同形式的接口的參數會被 mybatis 處理成不同的形式,可以參考 深入了解mybatis參數。由于沒有提供接口和類型相關的參數,因此無法根據類型實現通用的方法。

在最新的 3.4.5 版本中,providersqlsource 增加了一個額外可選的 providercontext 參數,這個類如下。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
 * the context object for sql provider method.
 *
 * @author kazuki shimizu
 * @since 3.4.5
 */
public final class providercontext {
 
 private final class<?> mappertype;
 private final method mappermethod;
 
 /**
  * constructor.
  *
  * @param mappertype a mapper interface type that specified provider
  * @param mappermethod a mapper method that specified provider
  */
 providercontext(class<?> mappertype, method mappermethod) {
  this.mappertype = mappertype;
  this.mappermethod = mappermethod;
 }
 
 /**
  * get a mapper interface type that specified provider.
  *
  * @return a mapper interface type that specified provider
  */
 public class<?> getmappertype() {
  return mappertype;
 }
 
 /**
  * get a mapper method that specified provider.
  *
  * @return a mapper method that specified provider
  */
 public method getmappermethod() {
  return mappermethod;
 }
 
}

有了這個參數后,就能獲取到接口和當前執行的方法信息,因此我們已經可以實現通用方法了。

下面是一個官方測試中的簡單例子,定義的通用接口如下。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface basemapper<t> {
 
 @selectprovider(type= oursqlbuilder.class, method= "buildselectbyidprovidercontextonly")
 @containslogicaldelete
 t selectbyid(integer id);
 
 @retention(retentionpolicy.runtime)
 @target(elementtype.method)
 @interface containslogicaldelete {
  boolean value() default false;
 }
 
 @retention(retentionpolicy.runtime)
 @target(elementtype.type)
 @interface meta {
  string tablename();
 }
}

接口定義了一個簡單的根據 id 查詢的方法,定義了一個邏輯刪除的注解、還有一個表名的元注解。

下面是 方法的實現。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public string buildselectbyidprovidercontextonly(providercontext context) {
 //獲取方法上的邏輯刪除注解
 final boolean containslogicaldelete = context.getmappermethod().
      getannotation(basemapper.containslogicaldelete.class) != null;
 //獲取接口上的元注解(不是實體)
 final string tablename = context.getmappertype().
      getannotation(basemapper.meta.class).tablename();
 return new sql(){{
  select("*");
  from(tablename);
  where("id = #{id}");
  if (!containslogicaldelete){
   where("logical_delete = ${constants.logical_delete_off}");
  }
 }}.tostring();
}

這里相比之前,可以獲取到更多的信息,sql 也不只是固定表的查詢,可以根據 @meta 注解制定方法查詢的表名,和原來一樣的是,最終還是返回一個簡單的 sql 字符串,仍然不支持動態 sql 的標簽。

下面是實現的接口。

?
1
2
3
4
@basemapper.meta(tablename = "users")
public interface mapper extends basemapper<user> {
 
}

上面實現的方法中,注解從接口獲取的,因此這里也是在 mapper 上配置的 meta 接口。

按照前面通用 mapper 中的介紹,在實現方法中是可以獲取 user 類型的,因此如果把注解定義在實體類上也是可行的。

現在看起來已經很不錯了,但是還不支持動態 sql,還不能緩存根據 sql 生成的 sqlsource,因此每次執行都需要執行方法去生成 sqlsource,仍然還有改進的地方,為了解決這個問題,我提交了兩個pr #1111,#1120,目前還在討論階段,真正實現可能要到 3.5.0 版本。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。

原文鏈接:https://blog.csdn.net/isea533/article/details/78493852

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 日韩在线观看中文字幕 | 国产精品亚洲第一区在线暖暖韩国 | 免费人成黄页网站在线一区二区 | 精品国产乱码久久久久久闺蜜 | 精品久久久av| av资源在线 | 在线播放91| 精品无码久久久久国产 | 国产精品视频久久久 | 国产成人精品一区二区三区视频 | 久久美女| 91精品国产日韩91久久久久久 | 玖玖精品在线 | 精品亚洲一区二区三区 | 99精品视频在线观看 | 91精品国产综合久久香蕉922 | 欧美视频精品在线观看 | 91视频在线播放视频 | 日本在线中文 | av片在线看 | 91九色视频pron | 群p在线观看 | 久久久久久综合 | 中文日韩在线 | 免费激情| 久久久久久av | 黄色美女网站在线观看 | 亚洲一区二区精品 | 免费的黄色网 | 麻豆网址| 毛片首页 | 日韩福利视频导航 | 国产精品福利在线 | 午夜黄色影院 | 粉嫩一区二区三区 | 国产精品久久久久久久午夜片 | 久色| 午夜私人影院在线观看 | 天天操一操 | 超碰97免费在线 | 久久99精品久久久久久水蜜桃 |