Mybatis是一种关系型数据库的ORM持久化框架,封装了JDBC和sqlmap,使java代码和DAO分离,提高了开发效率。
Mybatis和Hibernate对比 Hibernate和JPA:操作简便,开发效率高,不容易优化,反射操作太多影响性能。
Mybatis:轻量级,性能出色,开发效率稍低。
1 2 3 4 5 6 7 8 9 10 <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-boot-starter</artifactId > <version > 3.4.2</version > </dependency > <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-generator</artifactId > <version > 3.5.1</version > </dependency >
在resource文件夹下创建mybatis-config.xml配置文件。
—–表—–实体类——mapper接口—–映射文件–
java=数据库概念关系:实体类=表,类属性=字段,对象=记录/行,保持一致。xml映射文件的namespace命名域需要与mapper接口的全类名一致,sql语句的id要与mapper接口中的方法名一致。通过调用接口的方法来执行对应sql语句!
mapper接口的名字和mapper映射文件的名字保存一致,所在包包名保存一致,最好为mapper。
mybatis提供一个操作数据库的对象SqlSession,通过SqlSession.getMap获取mapper接口实现类的对象。
1 2 select * from user where username like "%"#{username}"%"; select * from user where username like '%${username}%';
在核心配置文件中mybaitis-plus配置:
1 2 3 4 5 6 7 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true type-aliases-package: com.xxx.xxx.entity mapper-locations: classpath:mapper/*.xml
使用步骤:
1、导入依赖
2、配置依赖
3、创建pojo
4、继承BaseMapper,创建mapper接口。
5、在spring应用程序上添加@MapperScan("xxx")
注解或在mapper接口上添加@Mapper
注解。
6、在service层中继承实现类ServiceImpl<BaseMapper, Object>,实现自定义接口。
1 2 3 @Service public class ProductServiceImpl extends ServiceImpl <ProductMapper, Product>
主键自增策略 mysql默认插入数据主键自增,可以在数据库中查看。为了弥补数据库默认自增的不足,比如会出现单点故障。MyBatis-Plus默认的主键策略是:ASSIGN_ID (使用了雪花算法)。mybaitis-plus需要在实体类属性上加上主键自增注解@TableId(type=IdType.AUTO)
。
1 @TableId(value = "product_id",type = IdType.AUTO)
type: AUTO自增、ID_WORKER默认唯一id、UUID全局唯一id。
字段主键@TableField(exist = false)
:该字段在数据库中不存在。
自动填充和乐观锁 自动填充 项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。我们可以使用MyBatis Plus的自动填充功能,完成这些字段的赋值工作。
实体上增加字段并添加自动填充注解
1 2 3 4 5 6 @TableField(fill = FieldFill.INSERT) private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime;
实现元对象处理器接口
1 2 3 4 5 6 7 8 9 10 11 12 @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); } }
乐观锁 当要更新一条记录的时候,希望这条记录没有被别人更新,也就是说实现线程安全的数据更新。
取出记录时,获取当前version更新时,带上这个version执行更新时, set version
修改实体类,添加@Version
注解
1 2 @Version private Integer version;
创建包config,创建文件MybatisPlusConfig.java, 此时可以删除主类中的 @MapperScan
扫描注解。
1 2 3 4 5 6 7 @Configuration @MapperScan("com.xxx.mapper") public class MpConfig { @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor () { return new OptimisticLockerInterceptor (); } }
${}和#{}获取参数值 字段名和属性名一致:ResultType
字段名和属性名不一致:给字段起别名,别名为属性名;在mybatis中配置驼峰命名map-underscore-to-camel-case: true;定义一个resultMap,通过property和column字段绑定
一对多 collection集合
1 2 3 4 5 6 7 8 9 10 11 <resultMap id ="DeptAndEmpResultMap" type ="Dept" autoMapping ="true" > <id column ="dept_id" property ="deptId" /> <collection property ="emps" ofType ="Emp" > <id property ="eid" column ="eid" > </id > <result property ="empName" column ="emp_name" > </result > <result property ="age" column ="age" > </result > <result property ="sex" column ="sex" > </result > <result property ="email" column ="email" > </result > </collection > </resultMap >
分步查询:
select:设置分步查询的唯一标识,mapper接口的全类名和方法名。
column:查询的字段。
ofType:查询中的集合内的数据类型。
多对一映射 resultMap级联属性赋值
;
<association>标签
;
1 2 3 4 5 6 7 <resultMap id ="MinMap" type ="Moneyin" autoMapping ="true" > <id column ="in_id" property ="inId" /> <association property ="zhanghu" javaType ="Zhanghu" autoMapping ="true" > </association > <association property ="categoryin" javaType ="Categoryin" autoMapping ="true" > </association > </resultMap >
分步查询
优点:实现了延迟加载,避免执行过多的sql语句。配置文件配置如下:
1 2 3 lazyLoadingEnabled =true aggressiveLazyLoading =false
当开启全局延迟加载后,查询可通过fetchType属性实现eager立即加载或lazy延迟加载。
动态SQL 尽管mybaitis-plus帮我们省去了动态sql的编写,但仍必要了解什么是动态sql。
本质:标签
作用:动态拼接sql语句
if标签
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <select id ="getEmp" resultMap ="Emp" > select * from t_emp where 1=1 <if test ="ename!=null and ename!=''" > ename = #{ename} </if > <if test ="age!=null and age!=''" > and age = #{age} </if > <if test ="email !=null and email!=''" > and email = #{email} </if > </select >
where标签
根据标签中的内容自动生成where关键字,并将条件内容前多余的and、or关键字去掉,后面的关键字无法忽略,如果没有内容则不会生成where。
trim标签
prefix|suffix,在标签内容的前后添加内容
choose、when标签
choose…when….otherwise =switch….case… default
foreach标签
collection:需要循环的数组和集合
item:表示数组和集合中的每一个元素
separator:循环之间的分割符
批量删除
1 2 3 4 5 6 7 <delete id="deleteMore"> delete from t_emp where eid in( <foreach collection="eids" item="eid" separator=","> #{ eid } </foreach> ) </delete>
批量增加
1 2 3 4 5 6 <insert id="insertMore"> insert into t_emp values <foreach collection="emps" item="emp" separator=","> (null,#{ emp.empName },#{ emp.age },#{ emp.sex },#{ emp.email },null) </foreach> </select>
sql标签
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <sql id ="minSql" > select mi.in_id, mi.single_in, mi.zhanghu_id, mi.categoryin_id, mi.date, mi.beizhu, z.zhanghu_name, z.zhanghu_money, z.create_time, cin.categoryin_name from moneyin mi join zhanghu z on mi.zhanghu_id=z.zhanghu_id join categoryin cin on mi.categoryin_id=cin.categoryin_id </sql > <include refid ="minSql" > </include >
缓存 针对查询,将数据暂时保存起来以备下一次查询。 cache hit ratio
一级缓存 默认开启,sqlSession级别,对于同一的sqlSession的查询,会从缓存中取对象,如果是同一查询则能够成功取出对象,若两次查询之间进行了一次增删改操作则会失效(相当于清除缓存),clearCache()手动清除缓存。
二级缓存 sqlSeesionFactory级别,通过同一sqlSeesionFactory创建的sqlSeesion查询的结果会被缓存,此后再执行相同的查询就会从缓存中取。
二级缓存开启条件:在核心配置文件中配置cacheEnabled=”true”;在映射文件中设置cache标签,在sqlSeesion关闭或提交后有效;查询的实体类型必须实现序列化接口。
查询顺序:先查二级缓存,没有命中再查一级缓存,最后查数据库 ,sqlSession关闭或提交后一级缓存保存到二级。
第三方缓存 代替二级缓存。
1)添加依赖
1 2 3 4 5 6 7 8 9 10 11 12 <dependency > <groupId > org.mybatis.caches</groupId > <artifactId > mybatis-ehcache</artifactId > <version > 1.2.11</version > </dependency > <dependency > <groupId > ch.qos.logback</groupId > <artifactId > logback-classic</artifactId > <version > 1.2.3</version > </dependency >
2)导入jar包
slf4j-api,mybatis-ehcache,ehcache,logback-classic。
3)配置第三方缓存配置文件ehchche.xml
4)设置二级缓存的类型
1 2 <cache type ="第三方缓存全类名" >
pagehelper分页插件 1、添加依赖
1 2 3 4 5 6 <dependency > <groupId > com.github.pagehelper</groupId > <artifactId > pagehelper-spring-boot-starter</artifactId > <version > 1.4.3</version > </dependency >
2、在核心配置文件中添加插件配置
1 2 3 4 5 6 7 pagehelper: helper-dialect: mysql reasonable: true support-methods-arguments: true params: count=countSql
分页插件的使用:
1 PageInfo<Product> listByPage (Integer page, Integer limit, String searchProductName) ;
index当前页起始索引,pageSize每页显示条数,pageNum当前页码,total总记录数,pages页数,prePage上一页页码,nextPage下一页页码,navigatePages导航页码数,navigatepageNums导航分页页码。
1 2 3 4 5 6 7 8 9 10 11 12 PageHelper.startPage(page,limit); QueryWrapper qw = new QueryWrapper ();if (!Objects.isNull(searchProductName)) { qw.like("pro_name" , searchProductName); } List products = this .list(qw);PageInfo<Product> pi = new PageInfo <>(products); return pi;
pageinfo对象:
1 2 3 4 5 PageInfo{ pageNum=1 , pageSize=5 , size=2 , startRow=1 , endRow=2 , total=2 , pages=1 , list=Page{ count=true , pageNum=1 , pageSize=5 , startRow=0 , endRow=5 , total=2 , pages=1 , reasonable=true , pageSizeZero=false } [ Product(productId=1 , proName=股票, proMoney=1000.00 , description=), Product(productId=2 , proName=黄金, proMoney=1000.00 , description=) ] , prePage=0 , nextPage=0 , isFirstPage=true , isLastPage=true , hasPreviousPage=false , hasNextPage=false , navigatePages=8 , navigateFirstPage=1 , navigateLastPage=1 , navigatepageNums=[ 1 ] }
查询 1)通过多个 id 批量查询
完成了动态sql的foreach的功能
1 2 3 4 5 6 7 @Test public void testSelect1 () { List<User> users=userMapper.selectBatchIds( Arrays.asList(1 , 2 , 3 )); System.out.println(users); }
2)简单的条件查询
通过map封装查询条件
注意:map中的key对应数据库中的列名。如:数据库user_id,实体类是userId,这时map的key需要填写user_id
1 2 3 4 5 6 7 8 9 @Test public void testSelect2 () { Map<String, Object> columnMap = new HashMap <>(); columnMap.put("name" ,"Jack" ); columnMap.put("age" ,20 ); List<User> users = userMapper.selectByMap(columnMap); System.out.println(users); }
MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能。
添加分页插件 配置类中添加@Bean
配置
1 2 3 @Bean public PaginationInterceptor paginationInterceptor () { return new PaginationInterceptor ();}
测试 selectPage 分页 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Test public void testSelectPage () { Page<User> page = new Page (1 ,3 ); Page<User> userPage = userMapper.selectPage(page, null ); long pages = userPage.getPages(); long current = userPage.getCurrent(); List<User> records = userPage.getRecords(); long total = userPage.getTotal();boolean hasNext = userPage.hasNext(); boolean hasPrevious = userPage.hasPrevious(); System.out.println(pages); System.out.println(current); System.out.println(records); System.out.println(total); System.out.println(hasNext); System.out.println(hasPrevious); }
测试 selecMapsPage 分页 当指定了特定的查询列时,希望分页结果列表只返回被查询的列,而不是很多null值。
测试selectMapsPage分页,结果集是Map
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void testSelectMapsPage () {Page<Map<String, Object>> page = newPage<>(1 , 5 ); Page<Map<String, Object>> pageParam = userMapper.selectMapsPage(page, null ); List<Map<String, Object>> records = pageParam.getRecords(); records.forEach(System.out::println); System.out.println(pageParam.getCurrent()); System.out.println(pageParam.getPages()); System.out.println(pageParam.getSize()); System.out.println(pageParam.getTotal()); System.out.println(pageParam.hasNext()); System.out.println(pageParam.hasPrevious()); }
性能分析插件 性能分析拦截器,用于输出每条SQL语句及其执行时间,可以设置最大执行时间,超过时间会抛出异常,该插件只用于开发环境,不建议生产环境使用。在MP中提供了对SQL执行的分析的插件,可用作阻断全表更新、删除的操作。
注意:该插件仅适用于开发环境,不适用于生产环境。
1、配置springboot:SQL分析插件
1 2 3 4 5 6 7 8 <configuration > <plugins > <plugin interceptor ="com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor" > <property name ="maxTime" value ="100" /> <property name ="format" value ="true" /> </plugin > </plugins > </configuration >
2、测试
1 2 3 4 5 6 7 @Test public void testSelectById () { User user = new User (); user.setId(2L ); User user1 = user.selectById(); System.out.println(user1); }
条件构造器 Wrapper : 条件构造抽象类,最顶端父类
AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
QueryWrapper : 查询条件封装
UpdateWrapper : Update 条件封装
AbstractLambdaWrapper : 使用Lambda 语法
LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
LambdaUpdateWrapper : Lambda 更新封装Wrapper
1 2 3 4 5 6 7 8 9 QueryWrapper<user> wrapper = new QueryWrapper <>(); wrapper.notLike("name" ,"e" ).likeRight("email" ,"t" ); List<Map<String,Object>> maps=userMapper.selectMaps(wrapper); maps.forEach(System.out::println); QueryWrapper<user> wrapper = new QueryWrapper <>(); wrapper.orderByDesc("id" ); List<User> users = userWrapper.selectList(wrapper); users.forEach(System.out::println);
MBG逆向工程 正向工程:先创建java实体类,框架负责根据实体类生成数据库表。
逆向工程:先创建数据库表,框架根据表生成java实体类,mapper接口和映射文件。
代码生成器 1、依赖导入
1 2 3 4 5 <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-generator</artifactId > <version > 最新版本</version > </dependency >
2、引入相应包
1 2 3 import com.baomidou.mybatisplus.generator.AutoGenerator;import com.baomidou.mybatisplus.generator.config. GlobalConfig
3、创建一个代码自动生成器对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 AutoGenerator mpg = new AutoGenerator ();GlobalConfig gc = new GlobalConfig ();String projectPath = System.getProperty("user.dir" );gc.setOutputDir(projectPath+"/src/main/java" ); gc.setAuthor("xxx" ); gc.setFileOverride(false ); gc.setOpen(false ); gc.setServiceName("%sService" ); gc.getIdType(IdType.ID_WORKER); gc.setDateType(Date.ONLY_DATE); mpg.setGlobalConfig(gc); DataSourceConfig dsc = new DataSourceConfig ();dsc.setUrl("jdbc:mysql://localhost:3306/xxx? useSSL=false&useUnicode=utf8" );dsc.setDriverName("com.mysql.cj.jdbc.Driver" ); dsc.setUsername("root" ); dsc.password("xxxxxx" ); dsc.setDbType(DbType.MYSQL); mpg.setDataSource(dsc); PackageConfig pc = new PackageConfig ();pc.setModuleName("xxx" ); pc.setParentName("com.xxx" ); pc.setEntity("entity" ); pc.setMapper("mapper" ); pc.setService("service" ); pc.setController("controller" ); mpg.setPackageInfo(pc); StrateryConfig sc = new StrateryConfig ();sc.setInclude("xxx" ,"xxx" ,"xxx" ); sc.setNaming(NamingStratery.underline_to_camel); sc.setColumnNaming(NamingStratery.underline_to_camel); sc.setEntityLombokModel(true ); sc.setLogicDeleteFieldName("deleted" ); TableFill xxCreate = new TableFill ("xx_create" ,FiledFill.INSERT);TableFill xxModified = new TableFill ("xx_modified" ,FiledFill.INSERT);ArrayList<TableFill> tfs = new ArrayList <>(); tfs.add(xxCreate); tfs.add(xxModified); sc.setTableFillList(tfs); sc.setVersionFieldName("version" ); sc.setRestControllerStyle(true ); mpg.setStratery(sc); mpg.execute();
注意:以上为旧版本,mybatis-plus-generator 3.5.1 及其以上版本对历史版本不兼容!3.5.1 以上的请参考 代码生成器新 和相关配置 。
目前支持两套生成的方式,一套使用SQL查询的方式是兼容旧的代码生成器核心逻辑使用,另一套使用驱动规范来读取元数据的方式,默认的使用元数据查询方式来生成代码,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 FastAutoGenerator.create("url" , "username" , "password" ) .globalConfig(builder -> { builder.author("xxx" ) .enableSwagger() .fileOverride() .outputDir("D://" ); }) .dataSourceConfig(builder -> builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> { int typeCode = metaInfo.getJdbcType().TYPE_CODE; if (typeCode == Types.SMALLINT) { return DbColumnType.INTEGER; } return typeRegistry.getColumnType(metaInfo); })) .packageConfig(builder -> { builder.parent("com.baomidou.mybatisplus.samples.generator" ) .moduleName("system" ) .pathInfo(Collections.singletonMap(OutputFile.xml, "D://" )); }) .strategyConfig(builder -> { builder.addInclude("t_xxx" ) .addTablePrefix("t_" , "c_" ); }) .templateEngine(new FreemarkerTemplateEngine ()) .execute();