MyBatis Plus学习
1. 入门案例
1、建立maven工程导入依赖
<!-- 引入mybatisplus依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
注:如果遇到maven仓库中有,但是导入不进去,在版本出爆红,则maven本地仓库中删除,重新下载即可。
2、建立实体类和数据库
实体类:
@Data
@TableName("user")
public class Account {
private Long id;
private String username;
private String password;
}
数据库:
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1271723566315397123 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'zhangsan', '123');
INSERT INTO `user` VALUES (2, 'lisi', '456');
INSERT INTO `user` VALUES (3, 'wangwu', '789');
3、Mapper接口
//@Component 如果使用component就要在springboot启动类处加上MapperScan
@Mapper
public interface UserMapper extends BaseMapper<Account> {
}
@SpringBootApplication
//@MapperScan("com.liuzeyu.mapper")
public class MybatisplusApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisplusApplication.class, args);
}
}
4、application.yml
配置数据源和SQL日志输出
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT
password: 809080
username: root
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
注意:username,password不能写成data-username和data-password,否则连接不到数据库。
5、测试函数
@SpringBootTest
public class MapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void test01(){
userMapper.selectList(null).forEach(account -> System.out.println(account));
}
@Test
public void test02(){
Account account = new Account();
account.setUsername("lizueyu");
account.setPassword("809080");
userMapper.insert(account);
}
}
2. MyBatis-plus常用注解
1、 @TableName
作用是当我们的实体类名和数据库表名不一致时,可以做到映射数据库表名、
@Data
@TableName("user")
public class Account {
private Long id;
private String username;
private String password;
}
2、@TableId
在mybatis-plus-annotation-3.3.2.jar
包下,是一个注解,作用于主键上面。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface TableId {
String value() default "";
IdType type() default IdType.NONE;
}
value:属性也是与数据库主键字段的映射。
其中IdType
是一个枚举,可以为type取值
AUTO(0),
NONE(1),
INPUT(2),
ASSIGN_ID(3),
ASSIGN_UUID(4),
/** @deprecated */
@Deprecated
ID_WORKER(3),
/** @deprecated */
@Deprecated
ID_WORKER_STR(3),
/** @deprecated */
@Deprecated
UUID(4);
看似有8个,其实也就前面5个,后面3个已经不建议使用了。
值 | 描述 |
---|---|
AUTO | 雪花算法实现数据库自增 |
NONE | MP set主键,雪花算法实现,没有自增 |
INPUT | 需要开发者手动赋值,如果没有,雪花算法实现,自增 |
ASSIGN_ID | MP 分配 ID,Integer,Long,String |
ASSIGN_UUID | MP 分配 UUID,String类型,也可手动赋值 |
- AUTO
- 当type设置为AUTO
@Data
@TableName("user")
public class Account {
@TableId(type = IdType.AUTO,value = "id")
private Long uid;
private String username;
private String password;
}
- 测试函数
@Test
public void test02(){
Account account = new Account();
account.setUsername("lizueyu222");
account.setPassword("809080");
userMapper.insert(account);
}
- 运行结果:
可见主键ID是根据雪花算法得到的一连串数字,此时实体类的id要使用Long类型,否则会报错。
- NONE
- 当type设置为NONE
@Data
@TableName("user")
public class Account {
@TableId(type = IdType.NONE,value = "id")
private Long uid;
private String username;
private String password;
}
- 测试函数
@Test
public void test02(){
Account account = new Account();
account.setUsername("lizueyu222");
account.setPassword("809080");
userMapper.insert(account);
}
- 运行结果
- INPUT
- 当type设置为INPUT
@Data
@TableName("user")
public class Account {
@TableId(type = IdType.INPUT,value = "id")
private Long uid;
private String username;
private String password;
}
- 测试函数
@Test
public void test02(){
Account account = new Account();
account.setUsername("lizueyu222");
account.setPassword("809080");
userMapper.insert(account);
}
- 运行结果
- ASSIGN_ID
- 当type设置为ASSIGN_ID
@Data
@TableName("user")
public class Account {
@TableId(type = IdType.ASSIGN_ID,value = "id")
private Long uid;
private String username;
private String password;
}
- 测试函数
@Test
public void test02(){
Account account = new Account();
account.setUsername("lizueyu555");
account.setPassword("809080");
userMapper.insert(account);
}
- 运行结果
和NONE类似,也可以自己手动赋值,但ASSIGN_ID支持的类型更多,有Integer,Long,String。
- ASSIGN_UUID
- 当type设置为ASSIGN_UUID
@Data
@TableName("user")
public class Account {
@TableId(type = IdType.ASSIGN_ID,value = "id")
private Long uid;
private String username;
private String password;
}
- 测试函数
@Test
public void test02(){
Account account = new Account();
account.setUsername("lizueyu555");
account.setPassword("809080");
userMapper.insert(account);
}
- 运行结果
出现了这个问题是因为类型不匹配,数组库的主键为bitint类型,不会支持String类型,所有要使用ASSIGN_UUID就必须先把主键类型改为varchar(1024)
需要把主键自增勾取消掉,因为String是不支持自增。
3、@TableField
-
value:非主键字段映射实体类属性
-
exist:表示是否实体类为数据库字段,不是的话标注false
@Data
@TableName("user")
public class Account {
@TableId(type = IdType.NONE,value = "id")
private Long uid;
private String username;
@TableField(exist = false)
private String password;
}
此时结果集将不会封装password属性
@Data
@TableName("user")
public class Account {
@TableId(type = IdType.NONE,value = "id")
private Long uid;
private String username;
@TableField(exist = false)
private String password;
@TableField(exist = true)
private String nickname;
}
此时将爆出异常,因为数据库表中并未存在nickname字段
3. select:表示是否查询该字段,默认为true
4. fill表示给数据库字段设置自动填充,常见的自动填充字段有create_Time,update_Time
如给实体类添加两个字段createTime,updateTime
@Data
@TableName("user")
public class Account {
@TableId(type = IdType.NONE,value = "id")
private Long uid;
private String username;
private String password;
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}
创建一个自动填充工具类:
/** * 自动填充处理器 */
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
为数据库添加字段
类型均为datetime,这样可以精确到秒,其中createTime对自动与create_Time做映射
测试类:
@Test
public void test02(){
Account account = new Account();
account.setUid(3333L);
account.setUsername("lizueyu444");
account.setPassword("809080");
userMapper.insert(account);
}
@Test
public void test03(){
Account account = userMapper.selectById(3333);
account.setUsername("xxxxxxxxxxx");
userMapper.updateById(account);
}
运行结果:
4、@Version
注解@Version的作用利用了乐观锁解决多线程并发。
# 线程1:
update user set username = liuzeyu, version = x where id = 3,version = 1;
# 线程2:
update user set username = duyangting, version x where id = 3,version = 1;
此时,线程1如果先执行,会将version改为2,然后线程2的条件就失效了,解决了并发数据的一致性问题。
为数据库添加字段version
添加乐观锁拦截器:
@Configuration
public class MyOptimisticLockConfig {
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
}
}
测试函数:
@Test
public void test04(){
//线程1
Account account = userMapper.selectById(3);
//线程2
Account account1 = userMapper.selectById(3);
account.setUsername("xyz");
account1.setUsername("opq");
userMapper.updateById(account);
}
运行结果:
可见此时version并没有改成3,实现了乐观锁解决并发的特点。
回顾乐观锁和悲观锁
-
乐观锁:总是以乐观心态,对数据也是安全也是很乐观的,每次去拿数据的时候都认为别人不会加锁,所以也都不会上锁,只要在更新的时候会判读数据有没有被修改过,所以乐观锁使用与多读的场景。
-
悲观锁:总是悲观的,对操作的数据也是悲观的,每次去拿数据的时候都认为有人会去修改它,所以都会加上锁,在此期间,其它线程若想访问,就必须进入堵塞状态,多用于更新数据的场合,这样才可以保证数据的安全性。例如:sychronized或同步代码块用的都是悲观锁。
5、@EnumeValue
@EnumeValue:做枚举类型与数据库字段名的映射关系。
- 实现方式1:使用配置
实体类添加枚举类型:
private StatusEnum status;
枚举类:
public enum StatusEnum {
Work(1,"工作"),
REST(0,"休息");
StatusEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
@EnumValue
private Integer code;
private String msg;
}
数据库添加status int字段:
测试函数:
@Test
public void test01(){
userMapper.selectList(null).forEach(account -> System.out.println(account));
}
运行结果:
2. 实现方式2:使用IEnum接口
为数据库添加age字段和实体类的age对应上
private AgeEnum age;
public enum AgeEnum implements IEnum<Integer> {
ONE(1,"一岁"),TWO(2,"两岁"),THREE(3,"三岁");
private Integer code;
private String msg;
AgeEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
@Override
public Integer getValue() {
return code;
}
}
测试函数:
@Test
public void test01(){
userMapper.selectList(null).forEach(account -> System.out.println(account));
}
运行结果:
6、@TableLoginc
@TableLoginc
作用是可以 “假删除”,怎么说呢,就可以可以做到数据库中有数据但是查不出来。
- 数据表添加字段deleted
- 实体类添加属性
@TableLogic
private Integer deleted;
- yam配置
global-config:
db-config:
logic-not-delete-value: 0 # 没有被删除的时候为
logic-delete-value: 1 # 已经被删除了
- 测试函数
@Test
public void test11(){
userMapper.deleteById(2);
}
- 测试结果:
可见此时deleted在数据库中被修改为1了,2这条数据字段就是被标记为 “删除”,已经不能被查询出来了
3. MyBatis-plus CRUD操作
1、 C (create)创建
@Test
public void test02(){
Account account = new Account();
account.setUid(3333L);
account.setUsername("lizueyu444");
account.setPassword("809080");
userMapper.insert(account);
}
2、 R (retrieval)查询
//1.无条件查询
//userMapper.selectList(null).forEach(account -> System.out.println(account));
//2.添加普通条件查询
QueryWrapper wrapper = new QueryWrapper();
//wrapper.eq("id",1); id = 1
//wrapper.lt("age",5); //小于 le:小于等于
//wrapper.gt("age",2); //大于 ge:大于等于
// Map<String,Object> map = new HashMap<>();
// map.put("age",2);
// map.put("id",2);
// wrapper.allEq(map); //取并集
// userMapper.selectList(wrapper).forEach(account-> System.out.println(account));
//3.模糊查询
//wrapper.like("username","wang");
//wrapper.likeLeft("username","liu"); %liu:尾部有liu
//wrapper.likeLeft("username","wang"); wang%:开头wang
//userMapper.selectList(wrapper).forEach(account-> System.out.println(account));
//4.通过map查找,map只能做等值操作
// Map<String,Object> map = new HashMap<>();
// map.put("age",2);
// map.put("id",2);
// userMapper.selectByMap(map).forEach(account-> System.out.println(account));
//5.将查询结果封装到map集合里
//userMapper.selectMaps(null).forEach(account-> System.out.println(account));
//6.in SQL查询
wrapper.inSql("age","select age from user where age > 1");
wrapper.inSql("id","select id from user where id < 4");
//userMapper.selectList(wrapper).forEach(account-> System.out.println(account));
//SQL拼接后:SELECT id AS uid,username,password,create_time,update_time,version,status,age FROM user
// WHERE (age IN (select age from user where age > 1) AND id IN (select id from user where id < 4))
//7.分页查询
Page<Account> page = new Page<>(1,2); //第1页2条数据
Page<Account> result = userMapper.selectPage(page, null);
// System.out.println(result.getSize());
// System.out.println(result.getTotal());
// result.getRecords().forEach(account -> System.out.println(account));
//将查询结果封装成map集合
Page<Map<String,Object>> pageMap = new Page<>(2,2);
//userMapper.selectMapsPage(pageMap,null).getRecords().forEach(accout-> System.out.println(accout));
3、 U (update)更新
@Test
public void test06(){
Account account = userMapper.selectById(5);
account.setUsername("liuzeyu");
userMapper.updateById(account);
QueryWrapper wrapper = new QueryWrapper();
wrapper.eq("username","liuzeyu");
account.setUsername("wangwu ");
userMapper.update(account,wrapper);
}
4 、D (delete)删除
@Test
public void test05(){
Map<String,Object> map = new HashMap<>();
map.put("id",3);
System.out.println(userMapper.deleteByMap(map));
}
4. MyBatis-plus 多表关联查询
-
创建产品表,添加外键
添加user_id为user表外键,注意的是user_id和user表的id必须类型和长度一致,否则将创建外键失败! -
创建产品VO类
@Data
public class ProductVO {
private String category;
private Integer count;
private String description;
private String userId;
private String username;
}
- UserMapper自定义SQL
@Select("select product.*,user.username from user,product " +
"where user.id = product.user_id and id=#{id}")
public List<ProductVO> listProduct(Integer id);
- 测试函数
@Test
public void test10(){
List<ProductVO> productVOS = userMapper.listProduct(3);
System.out.println(productVOS;
}
- 测试结果
5. MyBatis-plus自动生成
- 建立springboot项目导入MyBatis-plus自动生成依赖
<!-- 添加mybatis-plus自动生成-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.7</version>
</dependency>
</dependencies>
- 编写生产类
/** * 自动生成类 */
public class Main {
public static void main(String[] args) {
//1.创建autoGenerator
AutoGenerator autoGenerator = new AutoGenerator();
//2.配置数据源
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&charactEncoding=UTF-8&serverTimezone=UTC");
dataSourceConfig.setUsername("root");
dataSourceConfig.setPassword("809080");
autoGenerator.setDataSource(dataSourceConfig);
//3.全局配置
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setAuthor("liuzeyu12a");
globalConfig.setOpen(false);
globalConfig.setServiceImplName("%sService");
globalConfig.setOutputDir(System.getProperty("user.dir")+"/src/main/java");//生成地方
autoGenerator.setGlobalConfig(globalConfig);
//4.包信息
PackageConfig packageConfig = new PackageConfig();
packageConfig.setEntity("entity");
packageConfig.setModuleName("generator");
packageConfig.setService("service");
packageConfig.setServiceImpl("service.impl");
packageConfig.setMapper("mapper");
packageConfig.setController("controller");
packageConfig.setParent("com.liuzeyu");
//5.配置策略
StrategyConfig strategyConfig = new StrategyConfig();
strategyConfig.setEntityLombokModel(true); //Lombok
strategyConfig.setInclude("city"); //生成部分表
strategyConfig.setNaming(NamingStrategy.underline_to_camel); //驼峰转换
strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel); //驼峰转换
autoGenerator.setStrategy(strategyConfig);
autoGenerator.execute(); //执行自动生成
}
}
- 运算结果
- yml的配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT
password: 809080
username: root
thymeleaf:
suffix: .html
prefix: classpath:/templates/
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
- 编写controller层
@Controller
@RequestMapping("/city")
public class CityController {
@Autowired
private ICityService service;
@GetMapping("findAll")
public ModelAndView findAll(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("index");
modelAndView.addObject("list",service.list());
return modelAndView;
}
}
- 在templates下新建index.html
<!DOCTYPE html>
<html lang="en">
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<table>
<tr th:each="item : ${list}">
<td th:text="${item.cityId}"></td>
<td th:text="${item.cityName}"></td>
<td th:text="${item.countryId}"></td>
</tr>
</table>
</body>
</html>
- 测试从后台接收到数据
参考B站:https://www.bilibili.com/video/BV1yA411t782