这一篇从一个入门的基本体验介绍,再到对于 CRUD 的一个详细介绍,在介绍过程当中将涉及到的一些问题,例如逐渐策略,自动填充,乐观锁等内容说了一下,只选了一些重要的内容,还有一些没说起到,具体能够参考官网,简单的看完,其实会发现,若是遇到单表的 CRUD ,直接用 MP 确定舒服,若是写多表,仍是用 Mybatis 多点,毕竟直接写 SQL 会直观一点,MP 给个人感受,就是方法封装了不少,还有一些算比较是用的插件,可是可读性会稍微差一点,不过我的有我的的见解哇,祝你们国庆快乐 ~html
最初的 JDBC,咱们须要写大量的代码来完成与基本的 CRUD ,或许会在必定程度上使用 Spring 的 JdbcTemplate 或者 Apache 的 DBUtils ,这样一些对 JDBC 的简单封装的工具类。前端
再到后再使用 Mybatis 等一些优秀的持久层框架,大大的简化了开发,咱们只须要使用必定的 XML 或者注解就能够完成原来的工做java
JDBC --> Mybatis 无疑简化了开发者的工做,而今天咱们所讲额 MyBatis-Plus 就是在 MyBatis 的基础上,更加的简化开发,来一块儿看看吧!mysql
下列介绍来自官网:web
MyBatis-Plus(简称 MP)是一个 MyBatis 的加强工具,在 MyBatis 的基础上只作加强不作改变,为简化开发、提升效率而生。算法
咱们的愿景是成为 MyBatis 最好的搭档,就像魂斗罗中的 1P、2P,基友搭配,效率翻倍。spring
总之一句话:MyBatis-Plus —— 为简化开发而生!sql
按照官网的案例简单试一下 ,注:官网是基于 Springboot 的示例数据库
@Repository public interface UserMapper extends BaseMapper<User> { }
@Repository public interface UserMapper extends BaseMapper<User> { }
自行建立一个数据库便可,而后导入官网给出的案例表,而后插入以下数据apache
-- ---------------------------- -- Table structure for user -- ---------------------------- DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名', `age` int(11) NULL DEFAULT NULL COMMENT '年龄', `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮箱', PRIMARY KEY (`id`) USING BTREE ); -- ---------------------------- -- Records of user -- ---------------------------- INSERT INTO `user` VALUES (1, 'Jone', 18, 'test1@baomidou.com'); INSERT INTO `user` VALUES (2, 'Jack', 20, 'test2@baomidou.com'); INSERT INTO `user` VALUES (3, 'Tom', 28, 'test3@baomidou.com'); INSERT INTO `user` VALUES (4, 'Sandy', 21, 'test4@baomidou.com'); INSERT INTO `user` VALUES (5, 'Billie', 24, 'test5@baomidou.com');
若是没有接触过 SpringBoot,使用常规的 SSM 也是能够的,为了演示方便,这里仍是使用了SpringBoot,若是想在 SSM 中使用,一个注意依赖的修改,还一个就须要修改 xml 中的一些配置
引入 MyBatis-Plus-boot-starter 确定是没什么疑问的,一样咱们还须要引入,数据库链接的驱动依赖,还能够看须要引入 lombok,这里为了简便因此使用了它,若是不想使用,手动生成构造方法和 get set 便可
<!-- 数据库驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- MyBatis-Plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.0</version> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
如何进行数据库相关的信息在之前的SpringBoot文章已经说过了,这里强调一下:
mysql 5 驱动:com.mysql.jdbc.Driver
mysql 8 驱动:com.mysql.cj.jdbc.Driver
、还须要增长时区的配置
serverTimezone=GMT%2B8 spring.datasource.username=root spring.datasource.password=root99 spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
根据数据库字段建立出对应实体属性就好了,仍是提一下:上方三个注解,主要是使用了 lombok 自动的生成那些 get set 等方法,不想用的同窗直接本身按原来的方法显式的写出来就能够了~
@Data @AllArgsConstructor @NoArgsConstructor public class User { @TableId(type = IdType.AUTO) private Long id; private String name; private Integer age; private String email; }
代码以下,能够看到,咱们额外的继承了 BaseMapper,同时指定了泛型为 User
@Repository public interface UserMapper extends BaseMapper<User> { }
其实点进去 BaseMapper 看一下,你会发现,在其中已经定义了关于 CRUD 一些基本方法还有一些涉及到配合条件实现更复杂的操做,同时泛型中指定的实体,会在增删改查的方法中被调用
照这样说的话,好像啥东西都被写好了,若是如今想要进行一个简单的增删改查,是否是直接使用就好了
首先在测试类中注入 UserMapper,这里演示一个查询全部的方法,因此使用了 selectList
,其参数是一个条件,这里先置为空。
若是有哪些方法的使用不明确,咱们能够先点到 BaseMapper 中去看一下,down 下源码之后,会有一些注释说明
/** * 根据 entity 条件,查询所有记录 * * @param queryWrapper 实体对象封装操做类(能够为 null) */ List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
下面是测试查询全部的全代码
@SpringBootTest class MybatisPlusApplicationTests { @Autowired private UserMapper userMapper; @Test void contextLoads() { List<User> userList = userMapper.selectList(null); for (User user : userList) { System.out.println(user); } } }
控制台输出以下
User(id=1, name=Jone, age=18, email=test1@baomidou.com) User(id=2, name=Jack, age=20, email=test2@baomidou.com) User(id=3, name=Tom, age=28, email=test3@baomidou.com) User(id=4, name=Sandy, age=21, email=test4@baomidou.com) User(id=5, name=Billie, age=24, email=test5@baomidou.com)
通过一个简单的测试,感受仍是很香的,而之前在 Mybatis 中咱们执行sql语句时,是能够看到控制台打印的日志的,而这里显然没有,其实经过一行简单的配置就能够了
其实只须要在配置文件中加入短短的一行就能够了
MyBatis-Plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
打印以下:
固然你还能够经过一些 MyBatis Log 的插件,来快速的查看本身所执行的 sql
首先,先试试插入一个实体的操做,咱们选择使用了 insert 这个方法,下面是其定义:
/** * 插入一条记录 * * @param entity 实体对象 */ int insert(T entity);
@Test public void testInsert() { // 拟一个对象 User user = new User(); user.setName("理想二旬不止"); user.setAge(30); user.setEmail("ideal_bwh@163.com"); // 插入操做 int count = userMapper.insert(user); System.out.println(count); System.out.println(user); }
结果:
根据结果看到,插入确实成功了,可是一个发蒙的问题出现了,为啥 id 变成了一个 long 类型的值
对于主键的生成,官网有以下的一句话:
自3.3.0开始,默认使用雪花算法+UUID(不含中划线)
也就是说,由于上面咱们没有作任何的处理,因此它使用了默认的算法来当作主键 id
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit做为毫秒数,10bit做为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit做为毫秒内的流水号(意味着每一个节点在每毫秒能够产生 4096 个 ID),最后还有一个符号位,永远是0。
雪花算法 + UUID 因此基本是能够保证惟一的
固然除了雪花算法为,咱们还有一些别的主键生成的策略,例如 Redis、数据库自增
对于咱们以前经常使用的一种主键生成方式,通常都会用到数据库id自增
@TableId(type = IdType.AUTO)
再次插入,发现 id 已经实现了自增
@TableId 注解中的属性 Type 的值来自于 IdType 这个枚举类,其中我把每一项简单解释一下
AUTO(0)
:数据库 ID 自增(MySQL 正常,Oracle 未测试)
MyBatis-Plus.global-config.db-config.id-type=auto
NONE(1)
:该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
INPUT(2)
:用户输入 ID,也能够自定义输入策略,内置策略以下
使用时:
先添加 @Bean,而后实体类配置主键 Sequence,指定主键策略为 IdType.INPUT 便可,重点不说这个,有须要能够直接扒官网
@Bean public IKeyGenerator keyGenerator() { return new H2KeyGenerator(); }
ASSIGN_ID(3)
:雪花算法
ASSIGN_UUID(4)
:不含中划线的UUID
3.3.0 后,ID_WORKER(3)、ID_WORKER_STR(3)、UUID(4) 就已经被弃用了,前两个可使用 ASSIGN_ID(3)代替,最后一个使用 ASSIGN_UUID(4)代替
// 根据 whereEntity 条件,更新记录 int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper); // 根据 ID 修改 int updateById(@Param(Constants.ENTITY) T entity);
MyBatis-Plus 中的更新操做也是很是方便
举一种比较常见的一种状况,经过 id 值修改某些字段
传统作法会传一个修改后的对象,而后经过 #{}
设置具体更新的值和 id
<update id="updateById"> UPDATE user SET name=#{name}, portrait=#{portrait}, gender=#{gender}, telephone=#{telephone}, email=#{email} WHERE id=#{id} </update>
MyBatis-Plus 方式:
@Test public void testUpdate() { // 拟一个对象 User user = new User(); user.setId(1L); user.setName("理想二旬不止"); user.setAge(20); int i = userMapper.updateById(user); System.out.println(i); }
首先咱们给定了 id 值,同时又修改了姓名和年龄这两个字段,可是并非所有字段,来看一下执行效果
神奇的发现,咱们不须要在 sql 中进行设置了,全部的配置都被自动作好了,更新的内容和 id 都被自动填充好了
自动填充是填充什么内容呢?首先咱们须要知道,通常来讲表中的建立时间,修改时间,咱们老是但愿可以给根据插入或者修改的时间自动填充,而不须要咱们手动的去更新
可能之前的项目不是特别综合或须要等缘由,有时候也不会去设置建立时间等字段,写这部分是由于,在阿里巴巴的Java开发手册(第5章 MySQL 数据库 - 5.1 建表规约 - 第 9 条 )有明确指出:
【强制】表必备三字段:id, create_time, update_time。
说明:其中 id 必为主键,类型为 bigint unsigned、单表时自增、步长为 1。create_time, update_time
的类型均为 datetime 类型,前者如今时表示主动式建立,后者过去分词表示被动式更新
咱们能够经过直接修改数据库中对应字段的默认值,来实现数据库级别的自动添加语句
例如上图中我首先添加了 create_time, update_time 两个字段,而后将类型选择为 datetime
,又设置其默认值为 CURRENT_TIMESTAMP
注:更新时间字段中要勾选 On Update Current_Timestamp ,插入不用,使用 SQLYog 没问题,在 Navicat 某个版本下直接经过可视化操做可能会报错,没有此默认值,这种状况就把表先导出来,而后修改SQL,在SQL 中修改语句
create_time` datetime(0) DEFAULT CURRENT_TIMESTAMP(0) COMMENT '建立时间', update_time` datetime(0) DEFAULT CURRENT_TIMESTAMP(0) COMMENT '修改时间',
根据官网的自动填充功能的说明,其实咱们须要作的只有两点:
注:开始前,别忘了删除刚才数据库级别测试时的字段默认值等喔
首先填充字段注解:
@TableField(fill = FieldFill.INSERT)
@TableField(fill = FieldFill.INSERT_UPDATE)
@TableField(fill = FieldFill.INSERT) private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime;
FieldFill 说明:
public enum FieldFill { /** * 默认不处理 */ DEFAULT, /** * 插入时填充字段 */ INSERT, /** * 更新时填充字段 */ UPDATE, /** * 插入和更新时填充字段 */ INSERT_UPDATE }
接着建立自定义实现类 MyMetaObjectHandler,让其实现MetaObjectHandler,重写其 insertFill 和 updateFill 方法,打印日志就不说了,经过 setFieldValByName 就能够对字段进行赋值,源码中这个方法有三个参数
/** * 通用填充 * * @param fieldName java bean property name * @param fieldVal java bean property value * @param metaObject meta object parameter */ default MetaObjectHandler setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject) {...}
@Slf4j @Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { log.info("start insert fill ...."); setFieldValByName("createTime", new Date(), metaObject); setFieldValByName("updateTime", new Date(), metaObject); } @Override public void updateFill(MetaObject metaObject) { log.info("start update fill ...."); setFieldValByName("updateTime", new Date(), metaObject); } }
查看一下效果:
下面还有一些注意事项:
注意事项:
- 填充原理是直接给
entity
的属性设置值!!!- 注解则是指定该属性在对应状况下必有值,若是无值则入库会是
null
MetaObjectHandler
提供的默认方法的策略均为:若是属性有值则不覆盖,若是填充值为null
则不填充- 字段必须声明
TableField
注解,属性fill
选择对应策略,该声明告知MyBatis-Plus
须要预留注入SQL
字段- 填充处理器
MyMetaObjectHandler
在 Spring Boot 中须要声明@Component
或@Bean
注入- 要想根据注解
FieldFill.xxx
和字段名
以及字段类型
来区分必须使用父类的strictInsertFill
或者strictUpdateFill
方法- 不须要根据任何来区分可使用父类的
fillStrategy
方法
演示乐观锁插件前,首先补充一些基础概念:
打个比方,一张电影票价格为 30,老板告诉员工 A ,把价格上调到 50,员工 A 由于有事耽搁了两个小时,可是老板想了一会以为提价过高了,就想着订价 40 好了,正好碰到员工 B,就让员工 B 将价格下降 10 块
当正好两个员工都在操做后台系统时,两人同时取出当前价格,即 30 元,员工A 先操做后 价格变成了 50元,可是员工 B 又将30 - 10 ,即 变成20块,执行了更新操做,此时员工 B 的更新操做就会把前面的 50 元覆盖掉,即最终成为了 20元,虽然我心里毫无波澜,但老板却亏的一匹
意图:
当要更新一条记录的时候,但愿这条记录没有被别人更新
乐观锁实现方式:
实现这个功能,只须要两步:
@Version // 乐观锁的Version注解 private Integer version;
// 扫描 mapper 文件夹 @MapperScan("cn.ideal.mapper") @EnableTransactionManagement // 表明配置类 @Configuration public class MyBatisPlusConfig { // 乐观锁插件 @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInterceptor(); } }
说明:刚开始例如扫描 mapper 这样的注解就放在了启动类中,如今有了配置类,因此把它也移过来了
测试一下:
首先1号和2号获取到的数据是同样的,可是在1号尚未执行到更新的时候,2号抢先提交了更新操做,也就是说,当前真实数据已是被2号修改过的了,与1号前面获取到的不一致了
若是没有乐观锁,那么2号提交的更新会被1号的更新数据覆盖
// 测试更新 @Test public void testUpdate() { // 1号取得了数据 User user1 = userMapper.selectById(1L); user1.setName("乐观锁1号"); user1.setAge(20); user1.setEmail("ideal_bwh@xxx.com"); // 2号取得了数据 User user2 = userMapper.selectById(1L); user2.setName("乐观锁2号"); user2.setAge(30); user2.setEmail("ideal@xxx.com"); // 2号提交更新 userMapper.updateById(user2); // 1号提交更新 userMapper.updateById(user1); }
能够看到,在2号抢先执行后,1号就没有成功执行了
一样数据库中表的其 version 也从1变成了1
// 根据 ID 查询 T selectById(Serializable id); // 根据 entity 条件,查询一条记录 T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查询(根据ID 批量查询) List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList); // 根据 entity 条件,查询所有记录 List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查询(根据 columnMap 条件) List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap); // 根据 Wrapper 条件,查询所有记录 List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 根据 Wrapper 条件,查询所有记录。注意: 只返回第一个字段的值 List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 根据 entity 条件,查询所有记录(并翻页) IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 根据 Wrapper 条件,查询所有记录(并翻页) IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 根据 Wrapper 条件,查询总记录数 Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
@Test public void testSelectById(){ User user = userMapper.selectById(1L); System.out.println(user); }
说明:我这里使用的仍是最基本的写法,例如 List 能够用工具类建立 如:Arrays.asList(1, 2, 3)
遍历也彻底能够这样 users.forEach(System.out::println);
@Test public void testSelectByBatchId(){ List list = new ArrayList(); list.add(1); list.add(2); list.add(3); List<User> users = userMapper.selectBatchIds(list); for (User user : users){ System.out.println(user); } }
@Test public void testSelectByMap(){ HashMap<String, Object> map = new HashMap<>(); // 自定义要查询的字段和值 map.put("name","理想二旬不止"); map.put("age",30); List<User> users = userMapper.selectByMap(map); for (User user : users){ System.out.println(user); } }
经过日志的打印能够看到,它根据咱们的选择自动拼出了 SQL 的条件
==> Preparing: SELECT id,name,age,email,version,create_time,update_time FROM user WHERE name = ? AND age = ? ==> Parameters: 理想二旬不止(String), 30(Integer)
JavaWeb 阶段,你们都应该有手写过度页,配合 SQL 的 limit 进行分页,后面在 Mybatis 就会用一些例如 pageHelper 的插件,而 MyBatis-Plus 中也有一个内置的分页插件
使用前只须要进行一个小小的配置,在刚才配置类中,加入分页插件的配置代码
// 扫描咱们的mapper 文件夹 @MapperScan("cn.ideal.mapper") @EnableTransactionManagement @Configuration // 配置类 public class MyBatisPlusConfig { // 乐观锁插件 @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInterceptor(); } // 分页插件 @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); } }
接着就能够测试分页了
@Test public void testPage(){ // Page 参数: 参数1:当前页 ,参数1:页面大小 Page<User> page = new Page<>(2,3); userMapper.selectPage(page,null); List<User> users = page.getRecords(); for (User user : users){ System.out.println(user); } System.out.println(page.getTotal()); }
执行结果日志:
==> Preparing: SELECT id,name,age,email,version,create_time,update_time FROM user LIMIT ?,? ==> Parameters: 3(Long), 3(Long) <== Columns: id, name, age, email, version, create_time, update_time <== Row: 4, Sandy, 21, test4@baomidou.com, 1, null, null <== Row: 5, Billie, 24, test5@baomidou.com, 1, null, null <== Row: 1308952901602811906, 理想二旬不止, 30, ideal_bwh@163.com, 1, null, null <== Total: 3
如何实现一些条件相对复杂的查询呢?MyBatis-Plus 也给咱们提供了一些用法,帮助咱们方便的构造各类条件
其实前面你们应该就注意到了,在查询操做的可用方法中,参数中每每带有一个名叫 Wrapper<T> queryWrapper
的内容,这就是咱们要构造条件的重点
查询中最经常使用的就是 QueryWrapper
说明:
继承自 AbstractWrapper ,自身的内部属性 entity 也用于生成 where 条件
及 LambdaQueryWrapper, 能够经过 new QueryWrapper().lambda() 方法获取
实例化一个 QueryWrapper 后,经过调用一些内置的方法,就能够实现条件构造
例如咱们想要构造这样一个条件:查询邮箱不为空,且年龄小于 25 岁的用户
@Test void contextLoads() { // 实例化一个 QueryWrapper 对象 QueryWrapper<User> wrapper = new QueryWrapper<>(); // 进行具体条件构造 wrapper .isNotNull("email") .lt("age", 25); // 执行具体的查询方法,同时将 wrapper 条件做为参数传入 List<User> users = userMapper.selectList(wrapper); for (User user : users){ System.out.println(user); } }
看一下打印的日志:
==> Preparing: SELECT id,name,age,email,version,deleted,create_time,update_time FROM user WHERE deleted=0 AND (email IS NOT NULL AND age < ?) ==> Parameters: 25(Integer) <== Columns: id, name, age, email, version, deleted, create_time, update_time <== Row: 2, 理想, 22, ideal_bwh@xxx.com, 1, 0, 2020-09-26 15:06:09, 2020-09-26 21:21:52 <== Row: 4, Sandy, 21, test4@baomidou.com, 1, 0, null, null <== Row: 5, Billie, 24, test5@baomidou.com, 1, 0, null, null <== Total: 3
能够看到条件都被自动在 SQL 中构造出来了
使用的方式就这么简单,经过各类巧妙的构造就行了
下面是从官网摘取的各类构造方式:
allEq(Map<R, V> params) allEq(Map<R, V> params, boolean null2IsNull) allEq(boolean condition, Map<R, V> params, boolean null2IsNull)
个别参数说明:
params
: key
为数据库字段名,value
为字段值
null2IsNull
: 为true
则在map
的value
为null
时调用 isNull 方法,为false
时则忽略value
为null
的
allEq({id:1,name:"老王",age:null})
--->id = 1 and name = '老王' and age is null
allEq({id:1,name:"老王",age:null}, false)
--->id = 1 and name = '老王'
allEq(BiPredicate<R, V> filter, Map<R, V> params) allEq(BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull) allEq(boolean condition, BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
个别参数说明:
filter
: 过滤函数,是否容许字段传入比对条件中
params
与 null2IsNull
: 同上
allEq((k,v) -> k.indexOf("a") >= 0, {id:1,name:"老王",age:null})
--->name = '老王' and age is null
allEq((k,v) -> k.indexOf("a") >= 0, {id:1,name:"老王",age:null}, false)
--->name = '老王'
eq(R column, Object val) eq(boolean condition, R column, Object val)
eq("name", "老王")
--->name = '老王'
ne(R column, Object val) ne(boolean condition, R column, Object val)
ne("name", "老王")
--->name <> '老王'
gt(R column, Object val) gt(boolean condition, R column, Object val)
gt("age", 18)
--->age > 18
ge(R column, Object val) ge(boolean condition, R column, Object val)
ge("age", 18)
--->age >= 18
lt(R column, Object val) lt(boolean condition, R column, Object val)
lt("age", 18)
--->age < 18
le(R column, Object val) le(boolean condition, R column, Object val)
le("age", 18)
--->age <= 18
between(R column, Object val1, Object val2) between(boolean condition, R column, Object val1, Object val2)
between("age", 18, 30)
--->age between 18 and 30
notBetween(R column, Object val1, Object val2) notBetween(boolean condition, R column, Object val1, Object val2)
notBetween("age", 18, 30)
--->age not between 18 and 30
like(R column, Object val) like(boolean condition, R column, Object val)
like("name", "王")
--->name like '%王%'
notLike(R column, Object val) notLike(boolean condition, R column, Object val)
notLike("name", "王")
--->name not like '%王%'
likeLeft(R column, Object val) likeLeft(boolean condition, R column, Object val)
likeLeft("name", "王")
--->name like '%王'
likeRight(R column, Object val) likeRight(boolean condition, R column, Object val)
likeRight("name", "王")
--->name like '王%'
isNull(R column) isNull(boolean condition, R column)
isNull("name")
--->name is null
isNotNull(R column) isNotNull(boolean condition, R column)
isNotNull("name")
--->name is not null
in(R column, Collection<?> value) in(boolean condition, R column, Collection<?> value)
in("age",{1,2,3})
--->age in (1,2,3)
in(R column, Object... values) in(boolean condition, R column, Object... values)
in("age", 1, 2, 3)
--->age in (1,2,3)
notIn(R column, Collection<?> value) notIn(boolean condition, R column, Collection<?> value)
notIn("age",{1,2,3})
--->age not in (1,2,3)
notIn(R column, Object... values) notIn(boolean condition, R column, Object... values)
notIn("age", 1, 2, 3)
--->age not in (1,2,3)
inSql(R column, String inValue) inSql(boolean condition, R column, String inValue)
inSql("age", "1,2,3,4,5,6")
--->age in (1,2,3,4,5,6)
inSql("id", "select id from table where id < 3")
--->id in (select id from table where id < 3)
notInSql(R column, String inValue) notInSql(boolean condition, R column, String inValue)
notInSql("age", "1,2,3,4,5,6")
--->age not in (1,2,3,4,5,6)
notInSql("id", "select id from table where id < 3")
--->id not in (select id from table where id < 3)
groupBy(R... columns) groupBy(boolean condition, R... columns)
groupBy("id", "name")
--->group by id,name
orderByAsc(R... columns) orderByAsc(boolean condition, R... columns)
orderByAsc("id", "name")
--->order by id ASC,name ASC
orderByDesc(R... columns) orderByDesc(boolean condition, R... columns)
orderByDesc("id", "name")
--->order by id DESC,name DESC
orderBy(boolean condition, boolean isAsc, R... columns)
orderBy(true, true, "id", "name")
--->order by id ASC,name ASC
having(String sqlHaving, Object... params) having(boolean condition, String sqlHaving, Object... params)
having("sum(age) > 10")
--->having sum(age) > 10
having("sum(age) > {0}", 11)
--->having sum(age) > 11
func
func(Consumer<Children> consumer) func(boolean condition, Consumer<Children> consumer)
func(i -> if(true) {i.eq("id", 1)} else {i.ne("id", 1)})
or() or(boolean condition)
注意事项:
主动调用or
表示紧接着下一个方法不是用and
链接!(不调用or
则默认为使用and
链接)
eq("id",1).or().eq("name","老王")
--->id = 1 or name = '老王'
or(Consumer<Param> consumer) or(boolean condition, Consumer<Param> consumer)
or(i -> i.eq("name", "李白").ne("status", "活着"))
--->or (name = '李白' and status <> '活着')
and(Consumer<Param> consumer) and(boolean condition, Consumer<Param> consumer)
and(i -> i.eq("name", "李白").ne("status", "活着"))
--->and (name = '李白' and status <> '活着')
nested(Consumer<Param> consumer) nested(boolean condition, Consumer<Param> consumer)
nested(i -> i.eq("name", "李白").ne("status", "活着"))
--->(name = '李白' and status <> '活着')
apply(String applySql, Object... params) apply(boolean condition, String applySql, Object... params)
注意事项:
该方法可用于数据库函数 动态入参的params
对应前面applySql
内部的{index}
部分.这样是不会有sql注入风险的,反之会有!
apply("id = 1")
--->id = 1
apply("date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
--->date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
apply("date_format(dateColumn,'%Y-%m-%d') = {0}", "2008-08-08")
--->date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
last(String lastSql) last(boolean condition, String lastSql)
注意事项:
只能调用一次,屡次调用以最后一次为准 有sql注入的风险,请谨慎使用
last("limit 1")
exists(String existsSql) exists(boolean condition, String existsSql)
exists("select id from table where age = 1")
--->exists (select id from table where age = 1)
notExists(String notExistsSql) notExists(boolean condition, String notExistsSql)
notExists("select id from table where age = 1")
--->not exists (select id from table where age = 1)
// 根据 entity 条件,删除记录 int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper); // 删除(根据ID 批量删除) int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList); // 根据 ID 删除 int deleteById(Serializable id); // 根据 columnMap 条件,删除记录 int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
@Test public void testDeleteById(){ userMapper.deleteById(1L); }
@Test public void testDeleteBatchIds(){ List list = new ArrayList(); list.add(1308952901602811906L); list.add(1308952901602811907L); list.add(1308952901602811908L); list.add(1308952901602811909L); userMapper.deleteBatchIds(list); }
@Test public void testDeleteMap(){ HashMap<String, Object> map = new HashMap<>(); map.put("name","理想二旬不止"); userMapper.deleteByMap(map); }
删除这块再补充一下逻辑删除的概念,物理删除很好理解,就是实实在在的在数据库中删没了,可是逻辑删除,顾名思义只是逻辑上被删除了,实际上并无,只是经过增长一个字段让其失效而已,例如 deleted = 0 => deleted = 1
能够
应用的场景就是管理员想查看删除记录,在错误删除下,能够有逆转的机会等等
首先数据库增长 deleted 字段,同时建立其实体和注解
@TableLogic // 逻辑删除 private Integer deleted;
接着只须要在全局配置中配置便可
application.properties
# 配置逻辑删除 MyBatis-Plus.global-config.db-config.logic-delete-value=1 MyBatis-Plus.global-config.db-config.logic-not-delete-value=0
application.yml
MyBatis-Plus: global-config: db-config: logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后能够忽略不配置步骤2) logic-delete-value: 1 # 逻辑已删除值(默认为 1) logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
效果以下:
你会发现,逻辑删除会走一个更新操做,经过修改指定字段 deleted 的值为 0 实现咱们想要的效果
MyBatis-Plus 提供了一个很是便捷,有意思的内容,那就是代码的自动生成,咱们经过一些配置,就能够自动的生成 controller、service、mapper、pojo 的内容,而且接口或者注解等内容都会按照配置指定的格式生成。(提早准备好数据库和表)
首先除了 MyBatis-Plus 的依赖之外,还须要引入 swagger 和 velocity 的依赖,可是这二者实际上是可选的,能够选择不配置就不用引入了,默认使用 velocity 这个模板引擎,你们还能够换成别的
<dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency>
例如:
Velocity(默认):
<dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>latest-velocity-version</version> </dependency>
Freemarker:
<dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>latest-freemarker-version</version> </dependency>
Beetl:
<dependency> <groupId>com.ibeetl</groupId> <artifactId>beetl</artifactId> <version>latest-beetl-version</version> </dependency>
注意!若是您选择了非默认引擎,须要在 AutoGenerator 中 设置模板引擎。
AutoGenerator generator = new AutoGenerator(); // set freemarker engine generator.setTemplateEngine(new FreemarkerTemplateEngine()); // set beetl engine generator.setTemplateEngine(new BeetlTemplateEngine()); // set custom engine (reference class is your custom engine class) generator.setTemplateEngine(new CustomTemplateEngine()); // other config ...
下面就是一个主配置了,修改其中的数据库链接等信息,以及包的名称等等等执行就能够了
package cn.ideal; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.generator.AutoGenerator; import com.baomidou.mybatisplus.generator.config.DataSourceConfig; import com.baomidou.mybatisplus.generator.config.GlobalConfig; import com.baomidou.mybatisplus.generator.config.PackageConfig; import com.baomidou.mybatisplus.generator.config.StrategyConfig; import com.baomidou.mybatisplus.generator.config.po.TableFill; import com.baomidou.mybatisplus.generator.config.rules.DateType; import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; import java.util.ArrayList; /** * @ClassName: AutomaticCodeGenerate * @Description: TODO * @Author: BWH_Steven * @Date: 2020/10/2 21:29 * @Version: 1.0 */ public class AutomaticCodeGenerate { public static void main(String[] args) { // 须要构建一个代码自动生成器对象 AutoGenerator mpg = new AutoGenerator(); // 配置策略 // 全局配置 GlobalConfig gc = new GlobalConfig(); String projectPath = System.getProperty("user.dir"); gc.setAuthor("BWH_Steven"); gc.setOutputDir(projectPath + "/src/main/java"); gc.setOpen(false); gc.setFileOverride(false); // 是否覆盖 gc.setServiceName("%sService"); // 去Service的I前缀 gc.setIdType(IdType.ID_WORKER); gc.setDateType(DateType.ONLY_DATE); gc.setSwagger2(true); mpg.setGlobalConfig(gc); // 设置数据源 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding" + "=utf-8&serverTimezone=GMT%2B8"); dsc.setDriverName("com.mysql.cj.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("root99"); dsc.setDbType(DbType.MYSQL); mpg.setDataSource(dsc); // 包的配置 PackageConfig pc = new PackageConfig(); pc.setModuleName("test"); pc.setParent("cn.ideal"); pc.setEntity("entity"); pc.setMapper("mapper"); pc.setService("service"); pc.setController("controller"); mpg.setPackageInfo(pc); // 策略配置 StrategyConfig strategy = new StrategyConfig(); strategy.setInclude("user"); // 设置要映射的表名 strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel); strategy.setEntityLombokModel(true); // 自动lombok; strategy.setLogicDeleteFieldName("deleted"); // 自动填充配置 TableFill gmtCreate = new TableFill("create_time", FieldFill.INSERT); TableFill gmtModified = new TableFill("update_time", FieldFill.INSERT_UPDATE); ArrayList<TableFill> tableFills = new ArrayList<>(); tableFills.add(gmtCreate); tableFills.add(gmtModified); strategy.setTableFillList(tableFills); // 乐观锁 strategy.setVersionFieldName("version"); strategy.setRestControllerStyle(true); strategy.setControllerMappingHyphenStyle(true); mpg.setStrategy(strategy); mpg.execute(); //执行 } }
生成结构效果以下:
我简单贴两段生成的内容:
controller
package cn.ideal.test.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * <p> * 前端控制器 * </p> * * @author BWH_Steven * @since 2020-10-02 */ @RestController @RequestMapping("/test/user") public class UserController { }
entity
package cn.ideal.test.entity; import com.baomidou.mybatisplus.annotation.IdType; import java.util.Date; import com.baomidou.mybatisplus.annotation.Version; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.TableLogic; import com.baomidou.mybatisplus.annotation.TableField; import java.io.Serializable; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; /** * <p> * * </p> * * @author BWH_Steven * @since 2020-10-02 */ @Data @EqualsAndHashCode(callSuper = false) @ApiModel(value="User对象", description="") public class User implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "主键ID") @TableId(value = "id", type = IdType.AUTO) private Long id; @ApiModelProperty(value = "姓名") private String name; @ApiModelProperty(value = "年龄") private Integer age; @ApiModelProperty(value = "邮箱") private String email; @ApiModelProperty(value = "版本") @Version private Integer version; @TableLogic private Integer deleted; @ApiModelProperty(value = "建立时间") @TableField(fill = FieldFill.INSERT) private Date createTime; @ApiModelProperty(value = "修改时间") @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime; }
service
package cn.ideal.test.service; import cn.ideal.test.entity.User; import com.baomidou.mybatisplus.extension.service.IService; /** * <p> * 服务类 * </p> * * @author BWH_Steven * @since 2020-10-02 */ public interface UserService extends IService<User> { }
service 实现类
package cn.ideal.test.service.impl; import cn.ideal.test.entity.User; import cn.ideal.test.mapper.UserMapper; import cn.ideal.test.service.UserService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; /** * <p> * 服务实现类 * </p> * * @author BWH_Steven * @since 2020-10-02 */ @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { }
mapper
package cn.ideal.test.mapper; import cn.ideal.test.entity.User; import com.baomidou.mybatisplus.core.mapper.BaseMapper; /** * <p> * Mapper 接口 * </p> * * @author BWH_Steven * @since 2020-10-02 */ public interface UserMapper extends BaseMapper<User> { }
mapper XML
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="cn.ideal.test.mapper.UserMapper"> </mapper>
若是文章中有什么不足,欢迎你们留言交流,感谢朋友们的支持!
若是能帮到你的话,那就来关注我吧!若是您更喜欢微信文章的阅读方式,能够关注个人公众号
在这里的咱们素不相识,却都在为了本身的梦而努力 ❤
一个坚持推送原创开发技术文章的公众号:理想二旬不止