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

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

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

服務器之家 - 編程語言 - Java教程 - MyBatis通用Mapper實現原理及相關內容

MyBatis通用Mapper實現原理及相關內容

2021-06-24 10:45isea533 Java教程

今天小編就為大家分享一篇關于MyBatis通用Mapper實現原理及相關內容,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧

mybatis通用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
/**
 * 查詢全部結果
 * @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
/**
 * 重新設置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
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
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
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
<?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
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
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 的簡單處理過程。

最新的 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
/**
 * 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
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
@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

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 日本做暖暖视频高清观看 | 国产精品爱久久久久久久 | 亚洲精品久久久久久久久久吃药 | 中文字幕免费在线 | 欧美精产国品一二三区 | 欧美日韩一区二区三区在线观看 | 成人爽视频 | 成人国产精品久久 | 欧美一区二区在线观看 | 免费a视频在线观看 | 在线视频一区二区三区 | 欧美国产精品一区二区三区 | 日韩精品区 | av免费网站在线观看 | 国产精品综合 | 亚洲午夜精品毛片成人播放器 | 九九热视频精品在线观看 | 日韩精品极品视频在线观看免费 | 一区二区三区精品视频 | 久久天天躁狠狠躁夜夜免费观看 | 国产成人av在线 | 亚洲精品乱码久久久久久蜜桃91 | 久久久青草婷婷精品综合日韩 | 久久亚洲国产精品日日av夜夜 | 国产成人精品免高潮在线观看 | 亚洲国产精品福利 | 欧洲精品久久久 | 日韩亚洲一区二区 | 成人免费日韩 | 亚洲精品乱码8久久久久久日本 | 精品一区二区三区在线观看 | 日韩高清三区 | 日韩精品99| 成人av在线一区二区 | 国产一区二区三区在线免费看 | 国产综合精品一区二区三区 | 亚洲综合激情网 | 操操网站 | 久久亚洲一区 | 国产日韩精品一区 | 一区二区三区四区视频 |