- 一、快速入门
- 1.0 准备工作
- 1.1 添加依赖&配置文件
- 1.3 UserMapper接口
- 1.4 测试类&测试效果
- 二、常用注解
- 2.1 注解
- 2.2 拓展(全局配置)
- 三、CRUD 接口
- 3.1 Service CRUD 接口
- 3.2 Mapper CRUD 接口
- 3.3 拓展
- 3.3.1 QueryWrapper 包装器类
- 3.3.2 UpdateWrapper 包装器类
- 四、拓展功能
- 4.1 自动填充(FieldFill)
- 4.2 逻辑删除(@TableLogic)
- 五、插件
- 5.1 分页插件
- 5.1.1 添加配置
- 5.1.2 测试
- 5.1.3 自定义带条件的分页查询
- 5.1.4 总结: 分页查询到底怎么写?
- 5.2 代码生成器
一、快速入门 1.0 准备工作参考:MyBatis-Plus
1.1 添加依赖&配置文件参考 MyBatis-Plus中的建表语句!
springboot整合时:
org.springframework.boot spring-boot-starter-test test com.baomidou mybatis-plus-boot-starter 3.4.3.4 mysql mysql-connector-java 5.1.47
配置文件:application.yml
#MyBatis-Plus配置
mybatis-plus:
global-config:
db-config:
#全局主键生成策略
id-type: assign_id #该值为默认值(雪花算法生成主键id值)
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #日志输出
1.3 UserMapper接口【注】
- 别忘记在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹;
- 在User实体类上添加注解:@TableName("t_user") 映射数据库中的 t_user 表;
UserMapper接口要实现baseMapper接口;
public interface UserMapper extends baseMapper1.4 测试类&测试效果{ //Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能 }
@SpringBootTest
class MybatisPlusDemoApplicationTests {
//注入的Mapper接口自定义实现了baseMapper接口
@Autowired
private UserMapper userMapper;
@Test
void baseMapperTest1() {
int i = userMapper.insert(new User("zhangsan", 22, "123@qq.com"));
System.out.println(i);
}
}
当数据库中主键没有设置自增,并且在添加记录的时候没有手动设置主键id时,mybatis-plus默认会使用雪花算法给记录生成主键id;如下
参考:MyBatis-Plus注解
2.2 拓展(全局配置)全局配置: 要想影响所有实体的配置,可以设置全局主键配置;
#MyBatis-Plus 配置
mybatis-plus:
global-config:
db-config:
#全局主键生成策略
id-type: assign_id #该值为默认值(雪花算法生成主键id值)
三、CRUD 接口
3.1 Service CRUD 接口
参考:Service CRUD 接口
1)创建UserService接口
public interface UserService extends IService{ // IService:顶级 Service,内部调用baseMapper中的方法实现CRUD等操作 }
2)创建UserService的实现类
@Service public class UserServiceImpl extends ServiceImplimplements UserService { // ServiceImpl:IService 实现类( 泛型:M 是 mapper 对象,T 是实体 ) }
3)执行单元测试
@SpringBootTest
class MybatisPlusDemoApplicationTests {
//注入IService的实现类的Bean
@Autowired
private UserService userService;
@Test
void iServiceTest1(){
User user = userService.getById(2L);
System.out.println(user);
}
}
控制台运行效果:
- 如果我们在自己的XXXMapper中扩展了方法,这时候自己的XxxService中也要扩展一个方法来调用Mapper中的方法,在调用时,直接用baseMapper对象来调用就可以。(ServiceImpl实现类中注入了baseMapper的实现类即UserMapper)
//ServiceImpl部分源码如下 public class ServiceImpl, T> implements IService { //省略部分代码... @Autowired protected M baseMapper; //省略部分代码... }
3.2 Mapper CRUD 接口
参考:Mapper CRUD 接口
3.3 拓展 3.3.1 QueryWrapper 包装器类该类是对 Entity 实体对象的查询的封装类;
其继承和实现树结构如下:
小案例如下:
需求:删除email为test88@qq.com且age>31的用户;
@Test
void queryTest1(){
QueryWrapper wrapper = new QueryWrapper();
wrapper.gt("age",31);
wrapper.eq("email","test88@qq.com");
boolean b = userService.remove(wrapper);
System.out.println(b);
}
因为设置了逻辑删除所以此处的删除就变为修改了,如下:
继承或实现树结构如下:
小案例如下:
需求 1: 根据用户id修改他的姓名和年龄;(注意区别方法1和方法2的不同)
//方法1:
@Test
void updateTest1(){
UpdateWrapper wrapper = new UpdateWrapper<>();
//设置用户id=1的用户的name为Mike,age为30
wrapper.set("name","Mike").set("age",30).eq("id",1L);
boolean b = userService.update(wrapper);
System.out.println(b);
}
方法1中日志中的SQL语句如下:
【方法1的缺点】 造成字段的自动填充失效,没有更新update_time列的值。因为自动填充逻辑是建立在有实体对象传入的基础上。当然如果表中没有需要自动填充的字段,可以使用方法1. 接下来我们使用方法2让自动填充生效;
//方法2:
@Test
void updateTest2(){
User user = new User();
user.setName("火云天尊");
user.setAge(200);
//通过UpdateWrapper来封装where后面的条件子句,这里根据id修改 where id=xxx
UpdateWrapper wrapper = new UpdateWrapper<>();
wrapper.eq("id", 1L);
boolean b = userService.update(user, wrapper);
System.out.println(b);
}
方法2的SQL日志如下:
四、拓展功能 4.1 自动填充(FieldFill)此时发现设置了自动填充的字段的值在更新操作时发生了改变。
1)数据库表中添加两个属性;
2)实体类中添加对应属性值;
@TableName("t_user")
public class User {
@TableId
private Long id;
private String name;
private Integer age;
private String email;
//插入时填充字段(属性值和字段值不对应时要加value属性进行映射;看最下面的提示!)
@TableField(value = "create_time",fill = FieldFill.INSERT)
private LocalDateTime createTime;
//插入和更新时填充字段
@TableField(value = "update_time",fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
//省略构造方法和get、set方法
}
3)实现元对象处理器接口 -> 创建handler包,创建MymetaObjectHandler类;(别忘记@Component注解)
@Component //别忘记把该Handler注入到容器中!!!
public class MymetaObjectHandler implements metaObjectHandler {
//log4j日志
Logger log = LoggerFactory.getLogger(MymetaObjectHandler.class);
@Override
public void insertFill(metaObject metaObject) {
log.info("start insert fill ...");
//根据标注了FieldFill的属性在 插入 的时候填充值
this.strictInsertFill(metaObject,"createTime", LocalDateTime.class,LocalDateTime.now());
this.strictInsertFill(metaObject,"updateTime",LocalDateTime.class,LocalDateTime.now());
log.info("end insert fill ...");
}
@Override
public void updateFill(metaObject metaObject) {
log.info("start update fill ...");
//根据标注了FieldFill的属性在 修改 的时候填充值
this.strictUpdateFill(metaObject,"updateTime",LocalDateTime.class,LocalDateTime.now());
log.info("end update fill ...");
}
}
4)新增测试&修改测试;
//新增测试
@Test
void baseMapperTest1() {
int i = userMapper.insert(new User("张良", 26, "14bx6@qq.com"));
System.out.println(i);
}
//修改测试
@Test
void baseMapperTest2() {
User user = new User("张良良",23,"cc@qq.com");
user.setId(1474013526022393858L);
int i = userMapper.updateById(user);
System.out.println(i);
}
4.2 逻辑删除(@TableLogic)注意:MyBatis-Plus会自动将数据库中的下划线命名风格转化为实体类中的驼峰命名风格;所以如果命名规范的话@TableField注解的value属性可以不用写。
使用场景:可以进行数据恢复;
- 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据;
- 逻辑删除:假删除,将数据表中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录;
步骤:
1)数据库中创建逻辑删除状态列;
2)实体类中添加逻辑删除属性;
方式一:添加@TableLogic注解;(从3.3.0版本以后:方式一和方式二两个二选一)
//如果在配置文件中进行全局配置逻辑删除时,此注解可以不用写; //逻辑已删除值(默认为 1),逻辑未删除值(默认为 0) @TableLogic @TableField(value = "is_deleted") private Integer deleted;
方式二:全局配置;
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除的实体字段名
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
3)删除测试:删除功能被转变为更新功能;
-- 实际执行的SQL UPDATE t_user SET is_deleted=1 WHERe id=? AND is_deleted=0
4)查询测试:被逻辑删除的数据默认不会被查询;
//查询测试
@Test
void selectAll(){
//QueryWrapper : 封装查询条件的抽象类
List userList = userService.list(new QueryWrapper().select("id","name","age"));
for (User user : userList) {
System.out.println(user);
}
}
-- 实际执行的SQL SELECT id,name,age FROM t_user WHERe is_deleted=0五、插件 5.1 分页插件 5.1.1 添加配置
@SpringBootConfiguration
@MapperScan("com.ccbx.mapper") //可以将主类中的注解移到此处
public class MyBatisPlusConfig {
//MyBatisPlus拦截器
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加一个分页的内部拦截器
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
5.1.2 测试
封装一个类型为Page的分页参数对象,然后在Service类中调用baseMapepr接口中的selectPage方法,或者直接调用IService接口的page方法都可以。
@Test
void testPage1(){
//当前页和每页条数
long current = 2,size = 5;
Page page = new Page<>(current,size);
//包装查询条件
QueryWrapper wrapper = new QueryWrapper<>();
//查询三个字段,并且年龄大于22
wrapper.select("id","name","age").gt("age",22);
//此处查询返回的也是Page对象,跟上面的Page一模一样,只不过是完善了上面Page中的属性的值
userService.page(page, wrapper);
System.out.println("总记录数:"+page.getTotal());
System.out.println("当前页的数据集合:");
List userList = page.getRecords();
for (User user : userList) {
System.out.println(user);
}
}
日志中打印出来的SQL语句:
-- page.getTotal()方法调用的SQL语句 SELECT COUNT(*) AS total FROM t_user WHERe is_deleted = 0 AND (age > ?) -- page.getRecords()方法调用的SQL语句 SELECT id,name,age FROM t_user WHERe is_deleted=0 AND (age > ?) LIMIT ?,?5.1.3 自定义带条件的分页查询
方法1(简单查询): 不写SQL语句,用QueryWrapper来封装查询条件;如:
//包装查询条件 QueryWrapperwrapper = new QueryWrapper<>(); //查询三个字段,并且年龄大于22 wrapper.select("id","name","age").gt("age",22);
方法2(复杂查询):把SQL写到SQL映射文件中,在SQL中拼接查询条件;
步骤:
1)添加全局配置
mybatis-plus: # 加载类路径下的MyBatis 映射文件 mapper-locations: classpath:mappers/*Mapper.xml
2)UserMapper接口
public interface UserMapper extends baseMapper{ //自定义分页查询(这里封装查询条件的时候可以使用 实体类,VO类,Map集合等) Page queryByPage(Page page, @Param("conditionMap") Map cmap); }
3)UserMapper.xml 映射文件
4)UserService接口
public interface UserService extends IService{ //自定义的分页条件查询方法 public Page myQueryByPage(); }
5)UserServiceImpl实现类
@Service public class UserServiceImpl extends ServiceImplimplements UserService { //自定义的分页条件查询方法 public Page myQueryByPage(){ long current = 1, size = 5; Page page = new Page<>(current, size); Map map = new HashMap<>(); map.put("name","%J%"); map.put("age",18); baseMapper.queryByPage(page,map); return page; } }
6)测试方法:
@Test
void myPageTest(){
Page page = userService.myQueryByPage();
List userList = page.getRecords();
for (User user : userList) {
System.out.println(user);
}
}
5.1.4 总结: 分页查询到底怎么写?
-
没有带查询条件,直接在Controller中调用Service中的page( Page对象, null )
-
带查询条件,但是条件封装不复杂,可以在Service类中调用baseMapepr接口中的selectPage方法,或者直接调用IService接口的page方法都可以。
-
带复杂查询条件, 就在Mapper接口中新增方法,方法里一定带Page对象参数,和要封装的查询条件参数的map集合(实体类,Vo类都行);例如下面代码; 然后在Mapper.xml中来定义对应sql;
Page5.2 代码生成器queryByPage(Page page, @Param("conditionMap") Map cmap);
参考:代码生成器(旧)
@Test
void generateCode(){
// 1、创建代码生成器
AutoGenerator mpg = new AutoGenerator();
// 2、全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");//生成文件的输出目录
gc.setAuthor("cb");//开发人员
gc.setOpen(false); //生成后是否打开输出目录
gc.setServiceName("%sService"); //去掉Service接口的首字母I
gc.setIdType(IdType.AUTO); //主键策略
gc.setSwagger2(true);//开启Swagger2模式
mpg.setGlobalConfig(gc);
// 3、数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://127.0.0.1:3306/ymjr-core?serverTimezone=GMT%2B8&useSSL=false");
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
// 4、包配置
PackageConfig pc = new PackageConfig();
pc.setParent("com.ccbx.ymjr.core");//设置父目录
pc.setEntity("pojo"); //实体类所在包
mpg.setPackageInfo(pc);
// 5、策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略
strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
strategy.setEntityLombokModel(true); // lombok
strategy.setLogicDeleteFieldName("is_deleted");//逻辑删除字段名
strategy.setEntityBooleanColumnRemoveIsPrefix(true);//去掉布尔型属性的is_前缀
strategy.setRestControllerStyle(true); //restful api风格控制器 返回json
mpg.setStrategy(strategy);
// 6、执行
mpg.execute();
}



