Mybatis Plus詳解【一】

一、簡介

MybatisPlus可以節省大量時間,所有的CRUD代碼都可以自動化完成。MyBatis-Plus是一個MyBatis的增強工具,在 MyBatis 的基礎上只做增強不做改變,為簡化開發、提高效率而生。

特性:

  • 無侵入:只做增強不做改變,引入它不會對現有工程產生影響,如絲般順滑
  • 損耗小:啟動即會自動注入基本 CURD,性能基本無損耗,直接面向對象操作
  • 強大的 CRUD 操作:內置通用 Mapper、通用 Service,僅僅通過少量配置即可實現單表大部分 CRUD 操作,更有強大的條件構造器,滿足各類使用需求
  • 支持 Lambda 形式調用:通過 Lambda 表達式,方便的編寫各類查詢條件,無需再擔心字段寫錯
  • 支持主鍵自動生成:支持多達 4 種主鍵策略(內含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解決主鍵問題
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式調用,實體類只需繼承 Model 類即可進行強大的 CRUD 操作
  • 支持自定義全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 內置代碼生成器:采用代碼或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 層代碼,支持模板引擎,更有超多自定義配置等您來使用
  • 內置分頁插件:基于 MyBatis 物理分頁,開發者無需關心具體操作,配置好插件之后,寫分頁等同于普通 List 查詢
  • 分頁插件支持多種數據庫:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多種數據庫
  • 內置性能分析插件:可輸出 SQL 語句以及其執行時間,建議開發測試時啟用該功能,能快速揪出慢查詢
  • 內置全局攔截插件:提供全表 delete 、 update 操作智能分析阻斷,也可自定義攔截規則,預防誤操作

二、快速入門

:本文實驗環境為Spring Boot + MySQL。

1. 加入依賴

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.1.6</version><relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version></dependency><!--簡化bean代碼的?具包--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.4</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.6.4</version></dependency><dependency><groupId>org.testng</groupId><artifactId>testng</artifactId><version>RELEASE</version><scope>test</scope></dependency></dependencies>

2, 創建數據庫表

DROP TABLE IF EXISTS user;CREATE TABLE user(id BIGINT(20) NOT NULL COMMENT '主鍵ID',name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',age INT(11) NULL DEFAULT NULL COMMENT '年齡',email VARCHAR(50) NULL DEFAULT NULL COMMENT '郵箱',PRIMARY KEY (id)
);DELETE FROM user;INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');

3. 編寫application.yml

spring:datasource:username: rootpassword: 123456url: jdbc:mysql://localhost:3306/mybatis_plus?userUnicode=true&characterEncoding=utf-8driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

4. 引入log4j.properties

log4j.rootLogger=DEBUG,A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=[%t] [%c]-[%p] %m%n

5. 編寫pojo

@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("user")
public class User {private Long id;private String name;private Integer age;private String email;
}

6. 編寫mapper

@Mapper
public interface UserMapper extends BaseMapper<User> {
//所有的CRUD都已經完成,不需要像以前一樣配置一大堆文件:pojo->dao(連接mybatis,配置mapper.xml文件)->service->controller
}

7. 編寫啟動類

@SpringBootApplication
@MapperScan("com.shiftycat.mybatis_plus.mapper")
public class MybatisPlusApplication {public static void main(String[] args) {SpringApplication.run(MybatisPlusApplication.class, args);}}

8. 測試

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testSelect() {List<User> userList = userMapper.selectList(null);for (User user : userList) {System.out.println(user);}}
}

三、Mapper層的通用CRUD

1. 插入操作

// int insert(T entity);
@Test
public void testInsert() {User user = new User();user.setAge(18);user.setEmail("123456@qq.com");user.setName("張三");//返回的result是受影響的?數,并不是?增后的idint result = userMapper.insert(user);System.out.println(result);System.out.println(user.getId());
}
1)@TableId

通過數據庫可以看到,數據已經寫入到了數據庫,但是,id的值不正確,我們期望的是數據庫自增長,實際是MP生成了id的值寫入到了數據庫。

解決方案:自定義ID生成器

在復雜分布式系統中,往往需要大量的數據和消息進行唯一標識。比如支付寶每一個賬號在數據庫分表后都需要有一個唯一ID做標識。此時一個能夠生成全局唯一ID的系統是非常必要的。所以,生成的ID需要具備一下特點:

  1. 全局唯一性:不能出現重復的ID號,既然是唯一標識,這是最基本的要求。
  2. 趨勢遞增:在MySQL InnoDB引擎中使用的是聚集索引,由于多數RDBMS使用B-tree的數據結構來存儲索引數據,在主鍵的選擇上面我們應該盡量使用有序的主鍵保證寫入性能。
  3. 單調遞增:保證下一個ID一定大于上一個ID,例如事務版本號、IM增量消息、排序等特殊需求。
  4. 信息安全:如果ID是連續的,惡意用戶的扒取工作就非常容易做了,直接按照順序下載指定URL即可;如果是訂單號就更危險了,競對可以直接知道我們一天的單量。所以在一些應用場景下,會需要ID無規則、不規則。
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("user")
public class User {@TableId(type = IdType.AUTO) //指定id類型為自增長private Long id;private String name;private Integer age;private String email;
}

通過@TableId(type = IdType.AUTO)來設置主鍵ID自增長,ctrl+鼠標左鍵可查看剩下的type枚舉類型。主要主鍵生成方式有:

  • 數據庫ID自增(AUTO)

  • 雪花算法(ASSIGN_ID)

    這種方案是一種以劃分命名空間來生成ID的一種算法,這種方案把64-bit分別劃分成多段,分開來標示機器、時間等。使用41bit作為毫秒數,10bit作為機器的ID(5個bit是數據中心,5個bit的機器ID),12bit作為毫秒內的流水號(意味著每個節點在每毫秒可以產生4096個ID),最后還有一個符號位,永遠是0。

    優點

    • 毫秒數在高位,自增序列在低位,整個ID都是趨勢遞增的。
    • 不依賴數據庫等第三方系統,以服務的方式部署,穩定性更高,生成ID的性能也是非常高的。
    • 可以根據自身業務特性分配bit位,非常靈活。

    缺點

    • 強依賴機器時鐘,如果機器上時鐘回撥,會導致發號重復或者服務會處于不可用狀態。
  • UUID(ASSIGN_UUID)

    UUID(Universally Unique Identifier)的標準型式包含32個16進制數字,以連字號分為五段,形式為8-4-4-4-12的36個字符。

    優點

    • 性能非常高:本地生成,沒有網絡消耗。

    缺點

    • 沒有排序,無法保證趨勢遞增。
    • UUID往往使用字符串存儲,查詢的效率比較低。
    • 不易于存儲:UUID太長,16字節128位,通常以36長度的字符串表示,很多場景不適用。
    • 信息不安全:基于MAC地址生成UUID的算法可能會造成MAC地址泄露,這個漏洞曾被用于尋找梅麗莎病毒的制作者位置。
    • ID作為主鍵時在特定的環境會存在一些問題,比如做DB主鍵的場景下,UUID就非常不適用:
      • MySQL官方有明確的建議主鍵要盡量越短越好[4],36個字符長度的UUID不符合要求。
      • 對MySQL索引不利:如果作為數據庫主鍵,在InnoDB引擎下,UUID的無序性可能會引起數據位置頻繁變動,嚴重影響性能。

:自 3.3.0 開始,默認使用雪花算法+UUID(不含中劃線)

public enum IdType {// 數據庫ID?增AUTO(0),// 該類型為未設置主鍵類型NONE(1),// ?戶輸?ID,該類型可以通過??注冊?動填充插件進?填充INPUT(2),// 分配ID (主鍵類型為number或string)(雪花算法)ASSIGN_ID(3),// 分配UUID (主鍵類型為 string)ASSIGN_UUID(4);private final int key;private IdType(int key) {this.key = key;}public int getKey() {return this.key;}
}
2)@TableField

@TableField注解可以指定字段的?些屬性:

  1. value:指定屬性對應的數據庫表字段名。如果屬性名與表字段名一致,可以不用設置該屬性。
  2. exist:設置屬性是否為數據庫表字段,默認為true。如果設置為false,則表示該屬性不對應任何數據庫字段。
  3. select:設置查詢時是否查詢該屬性,默認為true。如果設置為false,則表示查詢時不查詢該屬性。
  4. insert:設置插入時是否插入該屬性,默認為true。如果設置為false,則表示插入時不插入該屬性。
  5. update:設置更新時是否更新該屬性,默認為true。如果設置為false,則表示更新時不更新該屬性。
  6. keepGlobalFormat:設置是否保持全局的命名規則,默認為false。如果設置為true,則屬性名將按照全局的命名規則進行轉換。
  7. condition:設置查詢條件,只有滿足條件的數據才會查詢到該屬性。

幾個常見的應用場景:

  1. 映射數據庫表字段名與實體類屬性名不一致

    通過設置value屬性,可以靈活地指定屬性對應的數據庫表字段名,避免在查詢和更新操作時發生字段名不匹配的錯誤。

  2. 設置插入和更新時需要忽略的字段

    通過設置insertupdate屬性,可以靈活地控制是否插入或者更新某個屬性,避免不必要的數據庫操作。

  3. 根據條件查詢指定的字段

    通過設置condition屬性,可以指定查詢時的條件,只有滿足條件的數據才會查詢到該字段,提高查詢性能。

@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("user")
public class User {@TableId(type = IdType.AUTO) //指定id類型為自增長private Long id;private String name;@TableField(select = false)private Integer age;@TableField(value = "email")private String mail;@TableField(exist = false)private String address;
}
3)@TableName

@TableName用來設置實體類對應的表明。如果我們不設置這個注解,我們操作的數據庫的表就由BaseMapper<User> 泛型決定(User)

  • value作用:value指定數據庫中的表名
@TableName(value="user")
public class User {......
}

另外的一種方法是在ymal文件中設置實體類所對應的表的統一前綴。

mybatis-plus:global-config:db-config:table-prefix: db_

2. 更新操作

1)根據主鍵更新
// 據 ID 修改
// int updateById(@Param("et") T entity);
@Test
public void testUpdateById() {User user = new User();user.setId(5L); //主鍵user.setAge(21); //更新的字段//根據id更新,更新不為null的字段this.userMapper.updateById(user);
}
2)根據條件更新
/*** 根據 whereEntity 條件,更新記錄* 正常的更新sql語句為:update 表名 set 字段 = 值 where 條件語句;* @param entity 實體對象 (set 條件值,可以為 null)* @param updateWrapper 實體對象封裝操作類(可以為 null,??的 entity ?于?成 where 語句)*/
// int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);
@Test
public void testUpdate1() {/*根據條件進行更新:將姓名為“張三”的年齡修改為30歲。*/User user = new User();//更新的字段user.setAge(30);//更新的條件QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.eq("name", "張三");//執?更新操作int result = this.userMapper.update(user, wrapper);System.out.println("result = " + result);
}@Test
public void testUpdate2() {// 方法2:通過UpdateWrapper進?更新//更新的條件以及字段UpdateWrapper<User> wrapper=new UpdateWrapper<>();wrapper.eq("name","張三").set("age",28);//執?更新操作int result=this.userMapper.update(null,wrapper);System.out.println("result = "+result);
}

3. 刪除操作

1)根據主鍵刪除
// int deleteById(T entity);
@Test
public void testDeleteById() {//執?刪除操作int result = this.userMapper.deleteById(5L);System.out.println("result = " + result);
}
2)根據條件刪除
// int deleteByMap(@Param("cm") Map<String, Object> columnMap);
@Test
public void testDeleteByMap1() {Map<String, Object> columnMap = new HashMap<>();columnMap.put("age", 28);columnMap.put("name", "張三");//將columnMap中的元素設置為刪除的條件,多個之間為and關系int result = this.userMapper.deleteByMap(columnMap);System.out.println("result = " + result);
}// int delete(@Param("ew") Wrapper<T> queryWrapper);
@Test
public void testDeleteByMap2() {User user = new User();user.setAge(18);user.setName("張三");//將實體對象進?包裝,包裝為操作條件QueryWrapper<User> wrapper = new QueryWrapper<>(user);int result = this.userMapper.delete(wrapper);System.out.println("result = " + result);
}
3)根據主鍵批量刪除
// int deleteBatchIds(@Param("coll") Collection<?> idList);
@Test
public void testDeleteByMap() {//根據id集合批量刪除int result = this.userMapper.deleteBatchIds(Arrays.asList(2L, 3L));System.out.println("result = " + result);
}

4. 查詢操作

1)根據主鍵查詢
// T selectById(Serializable id);
@Test
public void testSelectById() {//根據id查詢數據User user = this.userMapper.selectById(1L);System.out.println("result = " + user);
}
2)根據主鍵批量查詢
// List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);
@Test
public void testSelectBatchIds() {//根據id集合批量查詢List<User> users = this.userMapper.selectBatchIds(Arrays.asList(1L, 4L));for (User user : users) {System.out.println(user);}
}
3)根據條件查詢單個記錄

根據 entity 條件,查詢?條記錄,如果查詢結果超過一條會報錯。

default T selectOne(@Param("ew") Wrapper<T> queryWrapper) {List<T> list = this.selectList(queryWrapper);if (list.size() == 1) {return list.get(0);} else if (list.size() > 1) {throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());} else {return null;}
}
@Test
public void testSelectOne() {QueryWrapper<User> wrapper = new QueryWrapper<User>();wrapper.eq("name", "Jone");//根據條件查詢?條數據,如果結果超過?條會報錯User user = this.userMapper.selectOne(wrapper);System.out.println(user);
}
4)根據條件查詢總記錄數
// Long selectCount(@Param("ew") Wrapper<T> queryWrapper);
@Test
public void testSelectCount() {QueryWrapper<User> wrapper = new QueryWrapper<User>();wrapper.gt("age", 20); //年齡?于20歲//根據條件查詢數據條數Long count = this.userMapper.selectCount(wrapper);System.out.println("count = " + count);
}
5)根據條件查詢全部記錄
// List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);
@Test
public void testSelectList1() {QueryWrapper<User> wrapper = new QueryWrapper<User>();wrapper.gt("age", 23); //年齡?于23歲//根據條件查詢數據List<User> users = this.userMapper.selectList(wrapper);for (User user : users) {System.out.println("user = " + user);}
}// List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> queryWrapper);
// 方法返回List<Map<String, Object>>類型的值
@Test
public void testSelectList2() {QueryWrapper<User> wrapper = new QueryWrapper<User>();wrapper.gt("age", 23); //年齡?于23歲//根據條件查詢數據List<Map<String, Object>> users = this.userMapper.selectList(wrapper);users.forEach(System.out::println);
}
// List<Object> selectObjs(@Param("ew") Wrapper<T> queryWrapper);
// 只返回第一個字段的值
@Test
public void testSelectList3() {QueryWrapper<User> wrapper = new QueryWrapper<User>();wrapper.gt("age", 23); //年齡?于23歲//根據條件查詢數據List<Object> users = this.userMapper.selectList(wrapper);users.forEach(System.out::println);
}
6)根據條件查詢全部記錄并分頁

配置分頁插件

@Configuration
@MapperScan("com.shiftycat.mybatis_plus.mapper") //設置mapper接?的掃描包
public class MybatisPlusConfig {/*** 新的分頁插件,一緩和二緩遵循mybatis的規則,需要設置 MybatisConfiguration#useDeprecatedExecutor = false 避免緩存出現問題(該屬性會在舊插件移除后一同移除)*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;}
}
// <P extends IPage<T>> P selectPage(P page, @Param("ew") Wrapper<T> queryWrapper);
@Test
public void testSelectPage() {QueryWrapper<User> wrapper = new QueryWrapper<User>();wrapper.gt("age", 10); //年齡?于10歲//第一個參數:當前頁    第二個參數:每頁的條數Page<User> page = new Page<>(2, 1);//根據條件查詢數據IPage<User> iPage = this.userMapper.selectPage(page, wrapper);System.out.println("數據總條數:" + iPage.getTotal());System.out.println("總?數:" + iPage.getPages());System.out.println("分頁數據:" + iPage.getRecords());
}

四、SQL自動注入原理

1. MappedStatement 對象

在 Mybatis Plus 中,ISqlInjector 接口負責 SQL 的注入工作,AbstractSqlInjector 是它的實現類。

  • 首先,AbstractSqlInjector 抽象類執行 inspectInject 方法。在該方法中,使用this.getMethodList()獲取到實現類的列表,并且對于列表中的每一個元素運行inject()方法。

    public abstract class AbstractSqlInjector implements ISqlInjector {protected final Log logger = LogFactory.getLog(this.getClass());public AbstractSqlInjector() {}// inspectInject()方法public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {Class<?> modelClass = ReflectionKit.getSuperClassGenericType(mapperClass, Mapper.class, 0);if (modelClass != null) {String className = mapperClass.toString();Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());if (!mapperRegistryCache.contains(className)) {TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);// 獲取 CRUD 實現類列表List<AbstractMethod> methodList = this.getMethodList(mapperClass, tableInfo);if (CollectionUtils.isNotEmpty(methodList)) {methodList.forEach((m) -> {// inject()方法在這邊m.inject()(builderAssistant, mapperClass, modelClass, tableInfo);});} else {this.logger.debug(mapperClass.toString() + ", No effective injection method was found.");}mapperRegistryCache.add(className);}}}public abstract List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo);
    }
    
  • 其次,進入 inject() 方法。

    public void inject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {this.configuration = builderAssistant.getConfiguration();this.builderAssistant = builderAssistant;this.languageDriver = this.configuration.getDefaultScriptingLanguageInstance();// injectMappedStatement()方法在這邊this.injectMappedStatement(mapperClass, modelClass, tableInfo);}
    
  • 再進入 injectMappedStatement() 方法,ctrl+Alt選擇你執行的 CRUD 操作。以 SelectById 為例,生成了 SqlSource 對象,再將 SQL 通過 addSelectMappedStatementForTable() 方法添加到 meppedStatements 中。

    public class SelectById extends AbstractMethod {public SelectById() {this(SqlMethod.SELECT_BY_ID.getMethod());}public SelectById(String name) {super(name);}public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {SqlMethod sqlMethod = SqlMethod.SELECT_BY_ID;SqlSource sqlSource = new RawSqlSource(this.configuration, String.format(sqlMethod.getSql(), this.sqlSelectColumns(tableInfo, false), tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty(), tableInfo.getLogicDeleteSql(true, true)), Object.class);// addSelectMappedStatementForTable()方法在這邊return this.addSelectMappedStatementForTable(mapperClass, this.methodName, sqlSource, tableInfo);}
    }
    
  • 實現類是如何獲取到 meppedStatements 返回值:在 AbstractSqlInjector 抽象類 inspectInject 方法從 this.getMethodList() 方法獲取。

    public abstract class AbstractSqlInjector implements ISqlInjector {protected final Log logger = LogFactory.getLog(this.getClass());public AbstractSqlInjector() {}public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {......// 獲取 CRUD 實現類列表List<AbstractMethod> methodList = this.getMethodList(mapperClass, tableInfo);......}// getMethodList()方法public abstract List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo);
    }
    
  • DefaultSqlInjector 中的 getMethodList() 方法獲取CRUD實現類列表。

    public class DefaultSqlInjector extends AbstractSqlInjector {public DefaultSqlInjector() {}public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {Stream.Builder<AbstractMethod> builder = Stream.builder().add(new Insert()).add(new Delete()).add(new DeleteByMap()).add(new Update()).add(new SelectByMap()).add(new SelectCount()).add(new SelectMaps()).add(new SelectMapsPage()).add(new SelectObjs()).add(new SelectList()).add(new SelectPage());if (tableInfo.havePK()) {builder.add(new DeleteById()).add(new DeleteBatchByIds()).add(new UpdateById()).add(new SelectById()).add(new SelectBatchByIds());} else {this.logger.warn(String.format("%s ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.", tableInfo.getEntityType()));}return (List)builder.build().collect(Collectors.toList());}
    }
    

總結:項目啟動時,首先由默認注入器DefaultSqlInjector生成基礎 CRUD 實現類對象,其次遍歷實現類列表,依次注入各自的模板 SQL,最后將其添加至 mappedstatement

2. SQL 語句生成

SqlSource 通過解析SQL 模板、以及傳入的表信息主鍵信息構建出了 SQL 語句。

  • injectMappedStatement() 方法中,首先獲取在mapper.xml method id

    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {// 定義 mybatis xml method id, 對應 <id="xyz">SqlMethod sqlMethod = SqlMethod.SELECT_BY_ID;// 構造 id 對應的具體 xml 片段SqlSource sqlSource = new RawSqlSource(this.configuration, String.format(sqlMethod.getSql(), this.sqlSelectColumns(tableInfo, false), tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty(), tableInfo.getLogicDeleteSql(true, true)), Object.class);// 將 xml method 方法添加到 mybatis 的 MappedStatement 中return this.addSelectMappedStatementForTable(mapperClass, this.methodName, sqlSource, tableInfo);
    }
    
  • 根據 AbstractSqlInjector 抽象類的 inspectInject 方法中的 initTableInfo 方法獲取數據庫表信息。

    public static synchronized TableInfo initTableInfo(MapperBuilderAssistant builderAssistant, Class<?> clazz) {// 獲取數據庫表緩存信息TableInfo targetTableInfo = (TableInfo)TABLE_INFO_CACHE.get(clazz);Configuration configuration = builderAssistant.getConfiguration();// 獲取到緩存信息if (targetTableInfo != null) {Configuration oldConfiguration = targetTableInfo.getConfiguration();// 配置信息變化,則初始化if (!oldConfiguration.equals(configuration)) {targetTableInfo = initTableInfo(configuration, builderAssistant.getCurrentNamespace(), clazz);}return targetTableInfo;} else {// 沒有獲取到緩存信息,則初始化return initTableInfo(configuration, builderAssistant.getCurrentNamespace(), clazz);}
    }private static synchronized TableInfo initTableInfo(Configuration configuration, String currentNamespace, Class<?> clazz) {TableInfo tableInfo = new TableInfo(configuration, clazz);tableInfo.setCurrentNamespace(currentNamespace);GlobalConfig globalConfig = GlobalConfigUtils.getGlobalConfig(configuration);// 初始化表名相關String[] excludeProperty = initTableName(clazz, globalConfig, tableInfo);List<String> excludePropertyList = excludeProperty != null && excludeProperty.length > 0 ? Arrays.asList(excludeProperty) : Collections.emptyList();// 初始化字段相關initTableFields(configuration, clazz, globalConfig, tableInfo, excludePropertyList);// 自動構建 resultMaptableInfo.initResultMapIfNeed();globalConfig.getPostInitTableInfoHandler().postTableInfo(tableInfo, configuration);// 放入緩存TABLE_INFO_CACHE.put(clazz, tableInfo);TABLE_NAME_INFO_CACHE.put(tableInfo.getTableName(), tableInfo);// 緩存 lambdaLambdaUtils.installCache(tableInfo);return tableInfo;
    }
    
  • initTableName() 方法,獲取表名信息源碼中傳入了實體類信息 class,其實就是通過實體上的@TableName 注解拿到了表名。

    private static String[] initTableName(Class<?> clazz, GlobalConfig globalConfig, TableInfo tableInfo) {GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();// 通過實體上的@TableName 注解拿到了表名TableName table = (TableName)clazz.getAnnotation(TableName.class);String tableName = clazz.getSimpleName();String tablePrefix = dbConfig.getTablePrefix();String schema = dbConfig.getSchema();boolean tablePrefixEffect = true;String[] excludeProperty = null;if (table != null) {if (StringUtils.isNotBlank(table.value())) {tableName = table.value();if (StringUtils.isNotBlank(tablePrefix) && !table.keepGlobalPrefix()) {tablePrefixEffect = false;}} else {tableName = initTableNameWithDbConfig(tableName, dbConfig);}if (StringUtils.isNotBlank(table.schema())) {schema = table.schema();}if (StringUtils.isNotBlank(table.resultMap())) {tableInfo.setResultMap(table.resultMap());}tableInfo.setAutoInitResultMap(table.autoResultMap());excludeProperty = table.excludeProperty();} else {tableName = initTableNameWithDbConfig(tableName, dbConfig);}String targetTableName = tableName;if (StringUtils.isNotBlank(tablePrefix) && tablePrefixEffect) {targetTableName = tablePrefix + tableName;}if (StringUtils.isNotBlank(schema)) {targetTableName = schema + "." + targetTableName;}tableInfo.setTableName(targetTableName);if (CollectionUtils.isNotEmpty(dbConfig.getKeyGenerators())) {tableInfo.setKeySequence((KeySequence)clazz.getAnnotation(KeySequence.class));}return excludeProperty;
    }
    
  • 根據 AbstractSqlInjector 抽象類的 inspectInject 方法中的 initTableFields 方法獲取主鍵及其他字段信息。

    private static void initTableFields(Configuration configuration, Class<?> clazz, GlobalConfig globalConfig, TableInfo tableInfo, List<String> excludeProperty) {// 數據庫全局配置GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();PostInitTableInfoHandler postInitTableInfoHandler = globalConfig.getPostInitTableInfoHandler();Reflector reflector = tableInfo.getReflector();List<Field> list = getAllFields(clazz);// 標記是否讀取到主鍵boolean isReadPK = false;// 是否存在 @TableId 注解boolean existTableId = isExistTableId(list);boolean existTableLogic = isExistTableLogic(list);List<TableFieldInfo> fieldList = new ArrayList(list.size());Iterator var13 = list.iterator();while(var13.hasNext()) {Field field = (Field)var13.next();if (!excludeProperty.contains(field.getName())) {boolean isPK = false;boolean isOrderBy = field.getAnnotation(OrderBy.class) != null;if (existTableId) {// 主鍵ID初始化TableId tableId = (TableId)field.getAnnotation(TableId.class);if (tableId != null) {if (isReadPK) {throw ExceptionUtils.mpe("@TableId can't more than one in Class: \"%s\".", new Object[]{clazz.getName()});}initTableIdWithAnnotation(dbConfig, tableInfo, field, tableId);isReadPK = true;isPK = true;}} else if (!isReadPK) {isPK = isReadPK = initTableIdWithoutAnnotation(dbConfig, tableInfo, field);}if (isPK) {if (isOrderBy) {tableInfo.getOrderByFields().add(new TableFieldInfo(dbConfig, tableInfo, field, reflector, existTableLogic, true));}} else {TableField tableField = (TableField)field.getAnnotation(TableField.class);TableFieldInfo tableFieldInfo;if (tableField != null) {tableFieldInfo = new TableFieldInfo(dbConfig, tableInfo, field, tableField, reflector, existTableLogic, isOrderBy);fieldList.add(tableFieldInfo);postInitTableInfoHandler.postFieldInfo(tableFieldInfo, configuration);} else {tableFieldInfo = new TableFieldInfo(dbConfig, tableInfo, field, reflector, existTableLogic, isOrderBy);fieldList.add(tableFieldInfo);postInitTableInfoHandler.postFieldInfo(tableFieldInfo, configuration);}}}}tableInfo.setFieldList(fieldList);// 未發現主鍵注解,提示警告信息if (!isReadPK) {logger.warn(String.format("Can not find table primary key in Class: \"%s\".", clazz.getName()));}}
    

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/208565.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/208565.shtml
英文地址,請注明出處:http://en.pswp.cn/news/208565.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Windows 12 和 AI 計算機

據商業時報消息 &#xff0c;微軟計劃于 2024 年 6 月發布Windows 12。 新版本的操作系統將伴隨集成人工智能。 該數據基于廣達首席執行官林百里和宏基陳杰森在中國臺北醫療科技展上的發言。 雖然這篇文章沒有直接引用微軟高管的話&#xff0c;但它是根據他們的評論得出的結…

IDEA 社區版 add GitLab Account

問題 IntelliJ IDEA Community Edition 2023.3&#xff08;社區版&#xff09;在使用GitLab連接時&#xff0c;使用個人訪問令牌出現報錯&#xff0c;代碼&#xff1a; GraphQL error:[No such type ProjectMember,so it cant be a fraggment condition,Field id doesnt exis…

2023年最新prometheus + grafana搭建和使用

一、安裝prometheus 1.1 安裝 prometheus官網下載地址 sudo -i mkdir -p /opt/prometheus #移動解壓后的文件名到/opt/,并改名prometheus mv prometheus-2.45 /opt/prometheus/ #創建一個專門的prometheus用戶&#xff1a; -M 不創建家目錄&#xff0c; -s 不讓登錄 useradd…

Navicat 技術指引 | 適用于 GaussDB 分布式的數據遷移工具

Navicat Premium&#xff08;16.3.3 Windows 版或以上&#xff09;正式支持 GaussDB 分布式數據庫。GaussDB 分布式模式更適合對系統可用性和數據處理能力要求較高的場景。Navicat 工具不僅提供可視化數據查看和編輯功能&#xff0c;還提供強大的高階功能&#xff08;如模型、結…

單例模式---餓漢式、懶漢式

一、什么是單例模式 單例模式&#xff0c;指的是一個類中的對象只能有一個&#xff0c;它在內存中只會創建一次對象的設計模式。 二、餓漢式 public class SingleTon {// 私有的構造方法private SingleTon() {};// 1. 餓漢式private static SingleTon instance new SingleTon…

整數以及浮點數在內存中的存儲

一.整數在內存當中的存儲 數據在內存中是以十六進制補碼的形式進行存儲的。 原碼表示法簡單易懂&#xff0c;適用于乘法&#xff0c;但用原碼表示的數進行加減運算比較復雜&#xff0c;當兩數相加時&#xff0c;如果同號則數值相加&#xff0c;但是進行減法時要先比較絕對值的…

認知覺醒(六)

認知覺醒(六) 第二節 感性&#xff1a;頂級的成長竟然是“憑感覺” 人類生存于世&#xff0c;比拼的是腦力思維&#xff0c;但極少有人知道&#xff0c;我們的身體里還有一個更高級的系統&#xff0c;若能善用&#xff0c;成就非凡。 1941年&#xff0c;德軍對英國本土進行…

Neo4j介紹

1、Neo4j介紹 Neo4j 是一個圖數據庫管理系統&#xff0c;它專注于存儲和處理圖形結構的數據。圖數據庫是一類特殊的數據庫&#xff0c;用于有效地管理圖形數據模型&#xff0c;其中數據以節點、關系和屬性的形式存儲。 2、Neo4j特點 圖數據庫&#xff1a; Neo4j 是一種 NoSQ…

目標檢測器技術演進簡史

引言 目標檢測算法的發展已經取得了長足的進步&#xff0c;從早期的計算機視覺方法開始&#xff0c;通過深度學習達到了很高的準確度。在這篇博文中&#xff0c;我們將一起回顧一下這些算法的發展階段以及現代目標檢測系統中使用的主要方法。 我們首先回顧早期傳統的目標檢測…

大數據技術3:數據倉庫的ETL和分層模型

前言&#xff1a;我們先了解一下數據倉庫架構的演變過程。 1 、數據倉庫定義 數據倉庫是一個面向主題的&#xff08;Subject Oriented&#xff09;、集成的&#xff08;Integrate&#xff09;、相對穩定的&#xff08;Non-Volatile&#xff09;、反映歷史變化&#xff08;Time…

電商系統架構演進

聊聊電商系統架構演進 具體以電子商務網站為例&#xff0c; 展示web應用的架構演變過程。 1.0時代 這個時候是一個web項目里包含了所有的模塊&#xff0c;一個數據庫里包含了所需要的所有表&#xff0c;這時候網站訪問量增加時&#xff0c;首先遇到瓶頸的是應用服務器連接數&a…

深入體驗:山海鯨可視化軟件的獨特魅力

山海鯨可視化軟件是一款功能強大的數據可視化工具&#xff0c;作為該軟件的資深用戶&#xff0c;我深感其獨特的魅力和優勢。下面&#xff0c;我將從軟件特點、操作體驗、數據交互和實際應用場景等方面&#xff0c;為大家詳細介紹山海鯨可視化軟件。 首先&#xff0c;山海鯨可視…

解決Eslint和Prettier關于三元運算符的沖突問題

三元運算符Prettier的格式化 三元運算符Eslint的格式要求 解決辦法 // eslint加入配置&#xff0c;屏蔽標紅報錯indent: [error, 2, { ignoredNodes: [ConditionalExpression] }]效果

Nginx按指定格式記錄訪問日志

今天突然想起來一個日志的一個東西,因為拉項目無意中看到了日志文件的一些東西,現在不經常做后端了,加上其他的一些原因吧.有時候有些問題也沒想太多,馬馬虎虎就過了,后來想想還是要記錄一下這方面的處理過程吧: 一般我們作為開發人員關注的日志只是在應用程序層面的,我們稱它…

LSTM_預測價格問題_keras_代碼實操

0、問題描述 使用Bicton數據集&#xff0c;對close數據進行預測&#xff0c;使用60個數據點預測第61個數據點。 下載數據集&#xff1a;Bitcoin Historical Data 前期已經使用了MLP和RNN進行預測&#xff1a;這里 1、 沒有寫完&#xff0c;明天再寫&#xff1a;&#xff09;…

POJ 3735 Training little cats 動態規劃(矩陣的冪)

一、題目大意 我們有N只貓&#xff0c;每次循環進行K次操作&#xff08;N<100&#xff0c;K<100&#xff09;&#xff0c;每次操作可有以下三種選擇&#xff1a; 1、g i 給第i只貓1個食物 2、e i 讓第i只貓吃完它所有的食物 3、s i j 交換第i和j只貓的食物。 求出M次…

JS自己定義數組擴展方法 求和 和 最大值、最小值

相信有小伙伴看到這一個標題可能會想&#xff1a;現在都可以自己寫方法了嗎&#xff1f;這么炸裂。沒錯我們是可以自己寫方法的。 1.我們定義的這個方法&#xff0c;任何一個數組實例對象都可以使用 2.自定義的方法寫到 數組.propertype身上 最大值 const arr [1,2,3,4]Array…

銷售技巧培訓之如何提高手機銷售技巧

銷售技巧培訓之如何提高手機銷售技巧 隨著科技的迅速發展&#xff0c;手機已成為我們日常生活中不可或缺的一部分。作為一名手機銷售員&#xff0c;了解手機銷售技巧是必不可少的。本文將通過案例分析與實踐&#xff0c;為你揭示手機銷售的奧秘。 一、了解客戶需求 在銷售過程…

AWS Remote Control ( Wi-Fi ) on i.MX RT1060 EVK - 3 “編譯 NXP i.MX RT1060”( 完 )

此章節敘述如何修改、建構 i.MX RT1060 的 Sample Code“aws_remote_control_wifi_nxp” 1. 點擊“Import SDK example(s)” 2. 選擇“MIMXRT1062xxxxA”>“evkmimxrt1060”&#xff0c;并確認 SDK 版本后&#xff0c;點擊“Next>” 3. 選擇“aws_examples”>“aw…

在 Docker 容器中運行 macOS:接近本機性能,實現高效運行 | 開源日報 No.96

cxli233/FriendsDontLetFriends Stars: 2.6k License: MIT 這個項目是關于數據可視化中好的和不好的實踐&#xff0c;作者通過一系列例子解釋了哪些圖表類型是不合適的&#xff0c;并提供了如何改進或替代它們。主要功能包括展示錯誤做法以及正確做法&#xff0c;并提供相應代…