提示:常见的数据库持久层框架MP的常用功能的操作,特此摘录!!!
在SQL语句中获取Warpper拼接的SQL片段进行拼接添加Warpper类型的参数,并且要注意给其指定参数名
@Mapper
//@Mapper 注解,使当前的Mapper 接口,被Spring进行管理,不然需要在,启动类上声明 @MapperScan("com.xuguoguo.mapper") 类扫描指定包下,mapper接口文件;
public interface UserMapper extends BaseMapper<User> {
//Mapper 接口 extends集成 BaseMapper<T> 泛型对应的实体类;
// Ctrl+左击, 进入BaseMapper 中可以看到, MP 默认给对应实体类提供好的实现方法();
// 增删改查... 即各种的, 重载 CRUD 的操作;
//如果,BaseMapper<T> 中,没有提供的,后面还可以在,该 xxxMapper 文件中, 自定义自己需要的方法();
/** 自定义方法 **/
// 根据id查询对象;
User findMyUser(Long id);
// 自定义方法,使用MP 的Wrapper
List<User> findUsers(@Param(Constants.WRAPPER) Wrapper<User> wrapper); //wrapper 是MP包下的依赖~
}
<?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 -->
<mapper namespace="com.xuguoguo.mapper.UserMapper">
<!-- 指定sql 对应的方法名,返回的结果集类型; -->
<select id="findMyUser" resultType="com.xuguoguo.entity.User">
select * from user where id = #{id}
</select>
<!-- 指定sql 对应的方法名,返回的结果集类型; -->
<select id="findUsers" resultType="com.xuguoguo.entity.User">
select * from user ${ew.customSqlSegment}
</select>
</mapper>
/** 自定义方法查询 +MP的 wrapper 条件构造 **/
@Test
public void testfindusers(){
// 定义QueryWrapper 构造器,查询 age 大于 18 的数据;
QueryWrapper<User> query = new QueryWrapper<>();
query.gt("age", 18);
// 调用 自定义的方法(wrapper 参数);
List<User> users = userMapper.findUsers(query);
users.forEach(System.out::println);
}
1、mapper接口中的参数名 @Param(Constants.WRAPPER) Wrapper<User> wrapper 2、mapper.xml映射文件中中的拼接特殊字符 ${ew.customSqlSegment}
说明:
${ew.customSqlSegment}:拼接 where 后的语句(在动态sql中请勿处于 标签内)
${ew.sqlSelect}:拼接 select SQL 主体
${ew.sqlSet} :拼接 update 主体
${ew.sqlSegment} : 拼接where 后的语句
提示:这里需要分页设置
CREATE TABLE `orders` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`price` int(11) DEFAULT NULL COMMENT '价格',
`remark` varchar(100) DEFAULT NULL COMMENT '备注',
`user_id` int(11) DEFAULT NULL COMMENT '用户id',
`update_time` timestamp NULL DEFAULT NULL COMMENT '更新时间',
`create_time` timestamp NULL DEFAULT NULL COMMENT '创建时间',
`version` int(11) DEFAULT '1' COMMENT '版本',
`del_flag` int(1) DEFAULT '0' COMMENT '逻辑删除标识,0-未删除,1-已删除',
`create_by` varchar(100) DEFAULT NULL COMMENT '创建人',
`update_by` varchar(100) DEFAULT NULL COMMENT '更新人',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
/*Data for the table `orders` */
insert into `orders`
(`id`,`price`,`remark`,`user_id`,`update_time`,`create_time`,`version`,`del_flag`,`create_by`,`update_by`) values
(1,2000,'无',2,'2022-08-24 21:02:43','2022-08-24 21:02:46',1,0,NULL,NULL),
(2,3000,'无',3,'2022-08-24 21:03:32','2022-08-24 21:03:35',1,0,NULL,NULL),
(3,4000,'无',2,'2022-08-24 21:03:39','2022-08-24 21:03:41',1,0,NULL,NULL);
@Data
@NoArgsConstructor
@AllArgsConstructor
// 因为设置了全局的表前缀 tb_ 为了方便操作,@TableName 指定表;
@TableName("orders")
public class Orders {
// Orders 表属性:
private Long id;
private Integer price;
private String remark;
private Integer userId;
private LocalDateTime updateTime;
private LocalDateTime createTime;
private Integer version;
private Integer delFlag;
// 多表查询,User表扩展属性;
// MP 默认的CRUD 不对该属性进行sql映射,自定义Mapper 可以通过同名/取别名 自动映射;
@TableField(exist = false)
public String userName;
}
@Mapper
public interface OrdersMapper extends BaseMapper<Orders> {
// 自定义多表分页方法
public Page<Orders> selPageOrdUs(Page<Orders> page);
}
```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 -->
<mapper namespace="com.xuguoguo.mapper.OrdersMapper">
<!-- MP多表分页 -->
<select id="selPageOrdUs" resultType="com.xuguoguo.entity.Orders">
SELECT
o.*,u.`user_name` as userName
FROM orders o
INNER JOIN tb_user u
ON u.id = o.user_id
</select>
</mapper>
/** selectPage(); Mapper 多表分页 */
//定义 OrdersMapper 对象;
@Autowired
OrdersMapper ordersMapper;
@Test
public void testselPageOrdUs(){
// 定义MP 分页对象,并设置分页属性
Page<Orders> ordersPage = new Page<>();
// 每页行,当前页
ordersPage.setSize(2);
ordersPage.setCurrent(1);
// 进行查询,返回分页对象, 因为: 引用类型的实参改变形参会受影响~所以, ordersPage == ordersIPage
Page<Orders> ordersIPage = ordersMapper.selPageOrdUs(ordersPage);
System.out.println("ordersPage == ordersIPage是否相等:"+(ordersPage == ordersIPage));
// 返回的结果
System.out.println("获取总记录数");
System.out.println(ordersIPage.getTotal());
System.out.println("获取当前页的数据");
System.out.println(ordersIPage.getRecords());
}
ordersPage == ordersIPage是否相等:true
获取总记录数
3
获取当前页的数据
[Orders(id=1, price=2000, remark=无, userId=2, updateTime=2022-08-24T21:02:43, createTime=2022-08-24T21:02:46, version=1, delFlag=0, userName=admin), Orders(id=3, price=4000, remark=无, userId=2, updateTime=2022-08-24T21:03:39, createTime=2022-08-24T21:03:41, version=1, delFlag=0, userName=admin)]
MP 也为我们提供了Service 层的接口来完成 CRUD的操作:为什么MP 有了 Mapper接口, 还要 Service接口:
因为:为了方便开发程序现在程序大部分都是 三层架构``Dao持久 Service业务 Controller
控制【MVC架构】而定义的Mapper接口,很多时候又要在Service 里面进行重新调用
而,很多时候Servcie又很简单只能调用了Mapper的方法(); MP
为了简化开发者工作,对Service接口也进行了继承,这样对于一些简单的功能,只需要编写
Controller控制层就可以完成开发,开发者不需要在写简单的Service代码 还有,Service 中有很多是对Mapper
的整合方法();
在使用MP Service 的 CRUD 之前还是需要确保,Mapper 继承 BaseMapper
UserService.Java
创建 Service 接口,extends继承 IService<操作的实体类T>
IService类中,定义了Service的很多批量CRUD方法~
// Service接口 继承MP的 IService<T> T泛型,要CRUD对应映射的实体;
public interface UserService extends IService<User> {
}
UserServiceImpl.Java
// Spring注解,表示改类是一个 Service 业务逻辑类,并交给Spring容器管理;
@Service
// ServiceImpl 是 Service 的实现
// 继承MP 的ServiceImpl<对应的Mapper,映射表的实体类>
// 因为是 Service的实现, 实现对应的接口 implement Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
MPTest.Java
/** Service 的CRUD **/
// 从Spring容器中获取Service对象;
@Autowired
UserService userService;
// 只获取匹配的第一条数据;
@Test
public void testGetOne(){
LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery();
wrapper.gt(User::getAge, 28);
User one = userService.getOne(wrapper, false); // 第二参数指定为false,使得在查到了多行记录时,不抛出异常,而返回第一条记录
System.out.println(one);
}
就是正常的引用 Mapper
UserService.Java
// Service接口 继承MP的 IService<T> T泛型,要CRUD对应映射的实体;
public interface UserService extends IService<User> {
//自定义Service 实现:
List<User> selectAll();
}
UserServiceImpl.Java
// Spring注解,表示改类是一个 Service 业务逻辑类,并交给Spring容器管理;
@Service
// ServiceImpl 是 Service 的实现
// 继承MP 的ServiceImpl<对应的Mapper,映射表的实体类>
// 因为是 Service的实现, 实现对应的接口 implement Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
// 创建UserMapper 对象实例;
@Autowired
UserMapper userMapper;
@Override
public List<User> selectAll() {
// 直接调用Mapper 的查询全部~ 当然Service业务逻辑层,可以写很多更加复杂的操作...
return userMapper.selectList(null);
}
}
MP提供了一个代码生成器,可以让我们一键生成实体类,Mapper接口,Service,Controller等全套代码
<!-- Mybatsi-Plus 代码生成器依赖: -->
<!--mybatisplus代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<!--模板引擎-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
一般情况下,可以将这个文件放在 项目Util 包下,作为一个工具类使用:甚至,可以不声明在项目中,因为它可以指定 代码生成的地址…创建完成之后,将需要的东西拖到项目中也可以
MyGeneratorUtil.Java
public class MyGeneratorUtil {
@Test
public void generate() {
AutoGenerator generator = new AutoGenerator();
// 全局配置
GlobalConfig config = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
// 设置输出到的目录: 可以更改为任何路径 D盘 C盘...
// config.setOutputDir("D:/MP");
config.setOutputDir(projectPath + "/src/main/java");
// 生成的作者名
config.setAuthor("xuguoguo");
// 生成结束后是否打开文件夹
config.setOpen(false);
// 全局配置添加到 generator 上
generator.setGlobalConfig(config);
// 数据源配置: 配置自己的数据库要生成的数据库 用户/密码
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/plus?characterEncoding=utf-8&serverTimezone=UTC");
dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
dataSourceConfig.setUsername("root");
dataSourceConfig.setPassword("123");
// 数据源配置添加到 generator
generator.setDataSource(dataSourceConfig);
// 包配置, 生成的代码放在哪个包下
PackageConfig packageConfig = new PackageConfig();
packageConfig.setParent("com.xuguoguo");
// 包配置添加到 generator
generator.setPackageInfo(packageConfig);
// 策略配置
StrategyConfig strategyConfig = new StrategyConfig();
// 下划线驼峰命名转换
strategyConfig.setNaming(NamingStrategy.underline_to_camel);
strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);
// 开启lombok,生成的实体类上面就会又 lombok注解;
strategyConfig.setEntityLombokModel(true);
// 开启RestController
strategyConfig.setRestControllerStyle(true);
generator.setStrategy(strategyConfig);
generator.setTemplateEngine(new FreemarkerTemplateEngine());
// 开始生成
generator.execute();
}
}
在实际项目中表不仅仅会有开发中需要的功能字段有时候还会需要很多的附属字段:
更新时间 创建时间 创建人 更新人 逻辑删除列 乐观锁Version 备用1 备用2…
而这些字段需要我们手动进行维护会很麻烦,每个数据新增 修改都要进行手工维护;MP 提供了 自动填充 来完成对这些数据的操作
在对应字段上增加注解,@TableFieId 的 fill属性来设置字段的自动填充; Orders为例子fill 属性:是一个枚举FieldFill
MetaObjectHandler.Java
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
// 新增时候触发,并设置新增时候对应数据要赋的值
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime", LocalDateTime.now(), metaObject);
this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
}
// 修改时候触发,并设置修改时候对应数据要赋的值
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
}
}
https://baomidou.com/pages/4c6bcf/
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)
// 或者
this.strictInsertFill(metaObject, "createTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
// 或者
this.fillStrategy(metaObject, "createTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐)
// 或者
this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
// 或者
this.fillStrategy(metaObject, "updateTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)
}
}
/** MP 自动填充: **/
@Test
public void testinsertOrd(){
// 创建新增对象
Orders orders = new Orders();
orders.setPrice(1000);
orders.setRemark("无");
orders.setUserId(2);
// 执行新增
int insert = ordersMapper.insert(orders);
if(insert>0)
System.out.println("新增成功");
else
System.out.println("新增失败");
}
一般企业的数据都是不允许真实删除的,这样后面找都不好找所以
很多公司的数据库都会添加一个字段逻辑删除,用来判断数据是否删除,一般只有两个值:0|1
注意:3.3.0版本之前还需要在对应的字段上加上@TableLogic注解
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除的实体字段名, 3.3.0配置后可以不添加注解,之前的还需要添加注解 @Tablelogic)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
/** MP 逻辑删除 **/
@Test
public void delOrd(){
// 配置了逻辑删除之后,直接调用MP 的删除方法就是进行逻辑删除了! 注意: 自定义sql的操作还需要自己完成注意!
int i = ordersMapper.deleteById(1);
if(i>0)
System.out.println("逻辑删除成功");
else
System.out.println("逻辑删除失败");
}
为了避免多线程情况下数据紊乱需要对数据进行加锁,而当多个线程同时操作一个数据时候可能出现数据被重复修改的情况 典型的冲突
比较乐观 假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。乐观锁不能解决脏读的问题
通过为数据库表增加一个数字类型的 “version” 字段来实现,当读取数据时,将version字段的值一同读出,`数据每更新一次,对此version值加一
Update set version=version+1 where version = version 每次更新前都要判断传入的版本是否是现在最新版本!
比较悲观,认为一定会发送数据改变在对数据库的一条记录进行修改时,先直接加锁(数据库的锁机制),锁定这条数据,然后再进行操作. 同一时间只能,允许一个人来修改这条数据!
在读多写少的场景下,乐观锁比较适用,能够减少加锁操作导致的性能开销,提高系统吞吐量
在写多读少的场景下,悲观锁比较使用,否则会因为乐观锁不断失败重试,反而导致性能下降
参考官网编写即可:https://baomidou.com/pages/0d93c0/#optimisticlockerinnerinterceptor
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
Orders.Java 省略其它未更改代码;
// 添加乐观锁的注解,使用MP 的方式实现乐观锁 保证数据安全;
// version字段,类型只支持int,long,Date,Timestamp,LocalDateTime
@Version
private Integer version;
注意:
在更新前我们一定要先查询到version设置到实体类上再进行更新才能生效 传入的对象一定要携带 Version列有值 乐观锁插件仅支持updateById(id)与update(entity, wrapper)方法wrapper不能复用!会出现重复参数使用;
MPTest.Java
/** MP 乐观锁 */
// 操作前要去抱必须获取到数据最新的 version: 先查询在修改:
@Test
public void testupdVer(){
// 先查询:
Orders orders = ordersMapper.selectById(1);
// 设置更新列
orders.setPrice(123);
System.out.println(orders);
// 修改: 乐观锁插件仅支持`updateById(id)`与`update(entity, wrapper)`方法
int i = ordersMapper.updateById(orders);
if(i>0)
System.out.println("修改成功");
else
System.out.println("修改失败");
}
提示:本章节内容主体是讲解在使用数据库持久层的mp(MybatisPlus)框架的常用的应用功能,分页、自动填充、代码生成、逻辑删除、乐观锁等
多学一招,偷偷的努力,然后惊艳所有人!!!卷就完了……
本小节完毕,敬请期待后续更新(可留言需要学习哪方面的内容哈)!如果需要源码或者工具的朋友们可关注微信公众号"锅锅编程生活"或者扫描二维码关注回复关键字/后台留言获取即可!