养成习惯,先赞后看!!!
学习使用第三方组件心得:
因为公司的技术主管推荐我使用mybatis-plus插件之后,自己就跟着网上的教程学习了一下,学完之后,我尼玛是真的香
这里我就先粘贴官网上面说的那些优点
之后我就说一下 我自己的切身体会 吧
方便!方便!方便!..
真的太尼玛方便了,之前我们使用mybatis的时候就需要自己手动的去编写各种各样的SQL语句,并且还需要编写 pojo-dao-service-controller 这样的层级结构,使得我们的开发有一大部分的时间都是浪费在了这样的 重复性劳动
中,这就会严重打击我们的积极性,但是使用了mybatis-plus插件之后,是真的解放双手.
我自己的理解就是导入依赖,编写mapper接口继承BaseMapper之后,基本上大部分的数据库操作就已经结束了,真的是太方便了.
操作非常的简单
这里我本来以为也要像其他插件配置各种各样的bean,config之类的文件,但是实际配置之后发现真的非常简单.
我们只需要添加依赖
<!-- mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
创建我们的mapper接口继承BaseMapper
@Repository
public interface UserMapper extends BaseMapper<User> {
// 所有的crud已经完全写好,不需要配置一大堆文件
}
最后只需要创建一个config类去扫描我们的mapper接口的包就行了
//扫描我们的mapper文件夹
@MapperScan("com.rang.mapper")
@EnableTransactionManagement //开启事务支持
@Configuration//配置类
public class MyBatisPlusConfig {
}
就上述加起来都不到15行的代码就帮我们把之前我们那么繁琐的工作就完成了,你说这简不简单,我尼玛真的太简单了
内置的组件多
这里就说我自己平常最需要用到的一些组件吧.
乐观锁 : 当多个用户进行操作时,加锁就显得非常重要
分页组件 : 这个只要你的数据量还可以,分页显示,你就少不了吧
最后就是博主学习的教程就是 狂神说java 的教程,这是视频的地址:https://www.bilibili.com/video/BV17E411N7KN
以下的笔记中既有狂神的理念,同时也包含了我自己的一些思考.
并且我已经将整个项目上传到了我自己的GitHub上( 主要是为了练习一下如何把项目放到GitHub上面托管
)
这是链接地址:https://github.com/haha143/mybatis-plus
创建数据库mybatis-plus
创建user表:
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)
);
--真实开发中,version(乐观锁),deleted(逻辑删除),gmt_create,gmt_modified
插入数据:
DELETE FROM user;
INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, '[email protected]'),
(2, 'Jack', 20, '[email protected]'),
(3, 'Tom', 28, '[email protected]'),
(4, 'Sandy', 21, '[email protected]'),
(5, 'Billie', 24, '[email protected]');
编写项目,初始化项目
依赖:
<!-- 数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
mybatis-plus可以节省大量的代码,尽量不要同时导入mybatis和mybatis-plus
#这里我主要是针对的mysql5.7的版本
spring.datasource.username=root
spring.datasource.password=mysqladmin
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#mysql8的话就需要将驱动改成com.mysql.cj.jdbc.Driver,并且一定要在url里面添加时区serverTimezone这个属性,否则一定会报错
传统方式pojo-dao(连接mybatis,配置mapper.xml文件)-service-controller
使用mybatis-plus之后:
pojo
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
这里我使用了lombok插件,只要idea中下载一下lombok插件然后倒入依赖就能正常使用了
<!-- lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
mapper接口
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.rang.pojo.User;
import org.springframework.stereotype.Repository;
@Repository
public interface UserMapper extends BaseMapper<User> {
// 所有的crud已经完全写好,不需要配置一大堆文件
}
我么需要在mybatis-plus主启动类上来扫描我们的mapper接口
//扫描我们的mapper文件夹
@MapperScan("com.rang.mapper")
@SpringBootApplication
public class MybatisPlusApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisPlusApplication.class, args);
}
}
测试类中测试
@SpringBootTest
class MybatisPlusApplicationTests {
// 继承了BaseMapper.所有的方法都来自于父类
// 也可以编写自己的扩展方法
@Autowired
private UserMapper userMapper;
@Test
void contextLoads() {
// 参数是一个Wrapper,条件构造器,这里暂时用不到,先填null
// 查询全部用户
List<User>users=userMapper.selectList(null);
users.forEach(System.out::println);
}
}
我们所有的SQL现在是不可见的,我们需要看懂是怎么执行的
#配置日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
@Test
public void testinsert()
{
User user=new User();
user.setName("rangrang");
user.setAge(18);
user.setEmail("[email protected]");
int result=userMapper.insert(user);//帮我们自动生成id
System.out.println(result);
System.out.println(user);
}
@TableId(type = IdType.AUTO)//当选择是AUTO的时候,必须要保证数据库中表的该字段也是自增的,否则会报错
private Long id;
/**
* 数据库ID自增
*/
AUTO(0),
/**
* 该类型为未设置主键类型
*/
NONE(1),
/**
* 用户输入ID
* 该类型可以通过自己注册自动填充插件进行填充
*/
INPUT(2),
/* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */
/**
* 全局唯一ID (idWorker)
*/
ID_WORKER(3),
/**
* 全局唯一ID (UUID)
*/
UUID(4),
/**
* 字符串全局唯一ID (idWorker 的字符串表示)
*/
ID_WORKER_STR(5);
@Test
public void testUpdate(){
User user=new User();
user.setId(5L);
user.setName("关注公众号,谢谢");
//user.setAge(18);
int i=userMapper.updateById(user);
System.out.println(i);
}
创建时间,修改时间这些操作一般是自动化完成
方式一:数据库级别
在表中设置create_time与update_time
方式二:代码级别
删除之前的操作
在实体类的属性上添加注解
@TableField(fill = FieldFill.INSERT)
private Data createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Data UpdateTime;
编写处理器来处理这个注解即可!
package com.rang.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
@Slf4j
@Component //一定不要忘记将处理器添加到IOC容器之中
public class MyMetaObjectHandler implements MetaObjectHandler {
//插入时的填充策略
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill....");
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}
//更新时的填充策略
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill....");
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
编写完成之后我们去重新测试
测试更新
乐观锁,非常乐观,干啥都不会上锁,如果出现问题,再次更新值测试
悲观锁,非常悲观,每次测试都需要上锁
乐观锁实现方式:
1 查询时就先将version查出来 version=1
2,3 修改的时候将version也带上,这样就能使得资源安全
A线程
update set name="heihei" ,version=version + 1
where id=3 and version=1
B线程
update set name="heihei" ,version=version + 1
where id=3 and version=1
显然这样操作之后只有A线程能够正常执行,B线程就不能执行
测试乐观锁
修改表结构
修改实体类
@Version //乐观锁的Version注解
private Integer version;
注册组件
package com.rang.config;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
//扫描我们的mapper文件夹
@MapperScan("com.rang.mapper")
@EnableTransactionManagement //开启事务支持
@Configuration//配置类
public class MyBatisPlusConfig {
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
测试乐观锁成功(单线程必定成功)
//乐观锁成功
@Test
public void testOptimisticLocker(){
//查询用户信息
User user=userMapper.selectById(1L);
//修改用户信息
user.setName("要你命三千");
//执行更新操作
userMapper.updateById(user);
}
划重点!!!
这里提醒大家一下,大家可能会觉得我们有必要先查出用户的信息吗,能不能直接执行更新操作的,这里我们来测试一下,我们将测试的代码改成下面这样:
//乐观锁成功
@Test
public void testOptimisticLocker1(){
// User user=userMapper.selectById(1L);
User user=new User();
user.setId(1L);
user.setName("爱你三千遍");
userMapper.updateById(user);
}
测试之后我们发现
的确是执行了更新操作的,但是我们仔细看SQL语句能够发现他并有带上我们的version字段同时进行检测,所以说这样是不能够实现乐观锁的概念的,所以说本质上我们在执行操作的时候本质上都需要先查询一下该对象本质上就是查询出该对象的version字段,这样我们之后的操作才能够将version字段同时带进去进行检测,从而达到乐观锁的目的.
测试乐观锁失败
//乐观锁失败,多线程下
@Test
public void testOptimisticLocker2(){
//线程1
User user1=userMapper.selectById(1L);
user1.setName("要你命三千111");
//线程2插队
User user2=userMapper.selectById(1L);
user2.setName("要你命三千222");
userMapper.updateById(user2);
userMapper.updateById(user1);//如果没有乐观锁,就会覆盖插队线程的值
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ag8v0lKx-1600775508479)(C:\Users\瓤瓤\AppData\Roaming\Typora\typora-user-images\1600738078637.png)]
这里我们可以发现 userMapper.updateById(user1) 这个方法是没有被执行的,这里的主要原因就如下图所示:
查新单个信息
//查询单个用户
@Test
public void testSelectByid(){
User user=userMapper.selectById(1L);
System.out.println(user);
}
查询多个信息
// 查询多个用户
@Test
public void testSelectByBatchId(){
List<User> users=userMapper.selectBatchIds(Arrays.asList(1,2,3));
users.forEach(System.out::println);
}
条件查询
//条件查询通过map集合封装我们的数据
@Test
public void testSelectByBatchIds(){
HashMap<String,Object>map=new HashMap<>();
// 自定义条件查询
map.put("name","rangrang");
List<User> users= userMapper.selectByMap(map);
users.forEach(System.out::println);
}
其实我自己的分页查询的真个学习历程就是这样的
一开始就是通过 limit与offset
两个参数来进行查询的,并且还要另外写一个SQL语句来读取数据的总量,这样就会使得我们的SQL语句显得十分的臃肿就如下图所示:
之后同事介绍我使用了 pagehelper
这个插件之后,自己那时候也是觉得真香,还特地写了篇博客来记录了一下,大家有兴趣的可以去看看:前后端分离使用pagehelper
他会很明显的减少我们的SQL语句编写的工作量,不用再加上limit这些参数,直接写出select语句即可,并且不用再另外写一个查询数据总量的SQL语句,但是他本身还是需要我们传入两个必要的参数page与size,之后在controller层对他进行操作实现分页查询的功能.
但是使用了 mybatis-plus
之后你就知道什么叫更香了,mybatis-plus的分页插件就显得更加的简单方便,同样的我们只需要在配置类中注册分页插件,之后我们就可以直接使用了
//注册分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
之后我们就可以直接使用了
// 测试分页查询
@Test
public void testPage()
{
//第一个参数代表当前页
//第二个参数代表每页显示的数据条数
Page<User> page=new Page<>(1,5);
userMapper.selectPage(page,null);
page.getRecords().forEach(System.out::println);
}
这里Page有三个构造函数的如下图:
无参构造函数默认情况下就是显示第一页,每页显示的数据条数默认是10条
测试代码:
Page<User> page=new Page<>();
我们可以看到在执行分页查询之前,它先查询了数据的总条数,之后再执行的分页查询,并且可以看到显示的也的确是第一页的10条数据
之后我们测试第二种构造函数
Page<User> page=new Page<>(2,5);
可以看到整体流程也是和上面差不多,只是显示的数据条数和第几页发生了变化.
我们再测试一下第三种构造函数
Page<User> page=new Page<>(2,5,6);
显然分页查询仍然能够正常执行下去的,但是我们可以发现他是 直接执行分页查询操作 ,并没有像上面两步一样先去执行一次查询数据总量的操作,这就是因为我们已经给定了total这个变量名了,所以他就不会再去执行这个操作了,并且我们不需要一定给定正确的total值,只要随便给定一个了,很明显我们一共是有11条数据的,但是我输入6,他也是能够正常执行出来的.
删除单个
// 测试删除
@Test
public void testDeleteById(){
userMapper.deleteById(1307885269738921990L);
}
通过id删除多个
// 通过id批量删除
@Test
public void testDeleteBatchId(){
userMapper.deleteBatchIds(Arrays.asList(1307885269738921989L,1307885269738921988L));
}
通过map删除
// 通过map删除
@Test
public void testDeleteBymap(){
HashMap<String,Object>map=new HashMap<>();
map.put("name","rangrang");
userMapper.deleteByMap(map);
}
逻辑删除:只是改变了数据库中的某个字段,并不是真正的将数据从数据库中删除,就好比deleted字段deleted=0表示用户看不到该信息,但是管理员仍然能够看到deleted=1则表示用户和管理员都能看到,主要是为了维护数据
物理删除:直接从数据库中将数据删除,就是我们上述删除操作干的事
这样主要是为了防止数据的丢失,就好比我们电脑中的回收站.
首先我们需要修改数据库的结构,添加deleted这个字段,并且设置默认值是0,删除的话就改为1
之后我们需要修改user实体类
@TableLogic //逻辑删除注解
private Integer deleted;
之后我们需要去注册这个组件
@Bean
public ISqlInjector iSqlInjector(){
return new LogicSqlInjector();
}
之后我们就需要去设置一下属性
#配置逻辑删除
# 逻辑已删除值(默认为 1)
mybatis-plus.global-config.db-config.logic-delete-value: 1
# 逻辑未删除值(默认为 0)
mybatis-plus.global-config.db-config.logic-not-delete-value: 0
之后我们就可以来测试了
// 测试删除
@Test
public void testDeleteById(){
userMapper.deleteById(1L);
}
这里我们可以发现删除操作已经从之前的delete转换成了update操作了,既然这样我们再来看看这条数据我们还能不能查询出来
显然到这里我们就能发现数据没有查询出来,查询操作执行的时候就会再带上一个条件那就是deleted=0这个条件
导入插件
//性能分析插件
@Bean
@Profile({
"dev","test"})//设置dev test环境开发 主要就是为了保证我们的开发效率
public PerformanceInterceptor performanceInterceptor(){
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(1);//设置最大的超时时间,超过1秒就不执行
performanceInterceptor.setFormat(true);//SQL语句格式化显示
return performanceInterceptor;
}
因为我们设置了只有在开发与测试环境中才生效,所以我们需要去配置文件里面加上开发环境参数
#设置开发环境
spring.profiles.active=dev
这样我们就能够正常使用该组件了
测试使用
显然我们的程序是没有错的,但是还是报错了,提示我们的SQL语句执行的时间比我们设定的最大超时时间要长,所以报错了.
这里我们重新设置一下超时时间再来测试一下
这样我们就不报错,这主要就是帮助我们后序的SQL语句优化.
测试一
@Test
public void test01(){
//查询name不为空,邮箱不为空,年龄大于12的用户,
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper
.isNotNull("name")
.isNotNull("email")
.ge("age",12);
userMapper.selectList(wrapper).forEach(System.out::println);
}
测试二
@Test
public void test02(){
//查询name=tom的用户,
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper
.eq("name","tom");
System.out.println(userMapper.selectOne(wrapper));//查询一个数据
}
测试三
@Test
public void test03(){
//查询age在15到20的用户,
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper
.between("age",15,20);
System.out.println(userMapper.selectCount(wrapper));//查询的结果数
}
测试四
@Test
public void test04(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.inSql("id","select id from user where id<3");
List<Object> objects=userMapper.selectObjs(wrapper);
objects.forEach(System.out::println);
}
测试五
@Test
public void test05(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper
.notLike("name","e")
.likeRight("email","test");
List<Map<String,Object>> maps=userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}
测试六
@Test
public void test06(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper
.orderByDesc("id");
List<Map<String,Object>> maps=userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}
public class RangCode {
/**
*
* 读取控制台内容
*
*/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotBlank(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("jobob");
gc.setOpen(false);
// gc.setSwagger2(true); 实体属性 Swagger2 注解
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/ant?useUnicode=true&useSSL=false&characterEncoding=utf8");
// dsc.setSchemaName("public");
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("密码");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(scanner("模块名"));
pc.setParent("com.baomidou.ant");
mpg.setPackageInfo(pc);
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
// 如果模板引擎是 freemarker
String templatePath = "/templates/mapper.xml.ftl";
// 如果模板引擎是 velocity
// String templatePath = "/templates/mapper.xml.vm";
// 自定义输出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定义配置会被优先输出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
/*
cfg.setFileCreate(new IFileCreate() {
@Override
public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {
// 判断自定义文件夹是否需要创建
checkDir("调用默认方法创建的目录,自定义目录用");
if (fileType == FileType.MAPPER) {
// 已经生成 mapper 文件判断存在,不想重新生成返回 false
return !new File(filePath).exists();
}
// 允许生成模板文件
return true;
}
});
*/
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 配置模板
TemplateConfig templateConfig = new TemplateConfig();
// 配置自定义输出模板
//指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
// templateConfig.setEntity("templates/entity2.java");
// templateConfig.setService();
// templateConfig.setController();
templateConfig.setXml(null);
mpg.setTemplate(templateConfig);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
// 公共父类
strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");
// 写于父类中的公共字段
strategy.setSuperEntityColumns("id");
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix(pc.getModuleName() + "_");
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
}