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.mzph.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;并提供相应代…