MybatisAndPlus

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎…

什么是 MyBatis?

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

备注

一般用Mybatis是已经确定好了数据库的位置、账户和密码

安装

要使用 MyBatis, 只需将 mybatis-x.x.x.jar 文件置于类路径(classpath)中即可。

如果使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>x.x.x</version>
</dependency>

从 XML 中构建 SqlSessionFactory

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。 但也可以使用任意的输入流(InputStream)实例,比如用文件路径字符串或 file:// URL 构造的输入流。MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

XML 配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务管理器(TransactionManager)。后面会再探讨 XML 配置文件的详细内容,这里先给出一个简单的示例:

xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>

当然,还有很多可以在 XML 文件中配置的选项,上面的示例仅罗列了最关键的部分。 注意 XML 头部的声明,它用来验证 XML 文档的正确性。environment 元素体中包含了事务管理和连接池的配置。mappers 元素则包含了一组映射器(mapper),这些映射器的 XML 映射文件包含了 SQL 代码和映射定义信息。

从 SqlSessionFactory 中获取 SqlSession

既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。例如:

try (SqlSession session = sqlSessionFactory.openSession()) {
  Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
}

诚然,这种方式能够正常工作,对使用旧版本 MyBatis 的用户来说也比较熟悉。但现在有了一种更简洁的方式——使用和指定语句的参数和返回值相匹配的接口(比如 BlogMapper.class),现在你的代码不仅更清晰,更加类型安全,还不用担心可能出错的字符串字面值以及强制类型转换。

例如:

try (SqlSession session = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = mapper.selectBlog(101);
}

探究已映射的 SQL 语句

现在你可能很想知道 SqlSession 和 Mapper 到底具体执行了些什么操作,但 SQL 语句映射是个相当广泛的话题,可能会占去文档的大部分篇幅。 但为了让你能够了解个大概,这里会给出几个例子。

在上面提到的例子中,一个语句既可以通过 XML 定义,也可以通过注解定义。我们先看看 XML 定义语句的方式,事实上 MyBatis 提供的所有特性都可以利用基于 XML 的映射语言来实现,这使得 MyBatis 在过去的数年间得以流行。如果你用过旧版本的 MyBatis,你应该对这个概念比较熟悉。 但相比于之前的版本,新版本改进了许多 XML 的配置,后面我们会提到这些改进。这里给出一个基于 XML 映射语句的示例,它应该可以满足上个示例中 SqlSession 的调用。

<?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 namespace="org.mybatis.example.BlogMapper">
  <select id="selectBlog" resultType="Blog">
    select * from Blog where id = #{id}
  </select>
</mapper>

为了这个简单的例子,我们似乎写了不少配置,但其实并不多。在一个 XML 映射文件中,可以定义无数个映射语句,这样一来,XML 头部和文档类型声明部分就显得微不足道了。文档的其它部分很直白,容易理解。 它在命名空间 “org.mybatis.example.BlogMapper” 中定义了一个名为 “selectBlog” 的映射语句,这样你就可以用全限定名 “org.mybatis.example.BlogMapper.selectBlog” 来调用映射语句了,就像上面例子中那样:

Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);

你可能会注意到,这种方式和用全限定名调用 Java 对象的方法类似。这样,该命名就可以直接映射到在命名空间中同名的映射器类,并将已映射的 select 语句匹配到对应名称、参数和返回类型的方法。因此你就可以像上面那样,不费吹灰之力地在对应的映射器接口调用方法,就像下面这样:

BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);

第二种方法有很多优势,首先它不依赖于字符串字面值,会更安全一点;其次,如果你的 IDE 有代码补全功能,那么代码补全可以帮你快速选择到映射好的 SQL 语句。

配置

属性(properties)

这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。例如:

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>

设置好的属性可以在整个配置文件中用来替换需要动态配置的属性值。比如:

<dataSource type="POOLED">
  <property name="driver" value="${driver}"/>
  <property name="url" value="${url}"/>
  <property name="username" value="${username}"/>
  <property name="password" value="${password}"/>
</dataSource>

类型别名(typeAliases)

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:

<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
  <typeAlias alias="Comment" type="domain.blog.Comment"/>
  <typeAlias alias="Post" type="domain.blog.Post"/>
  <typeAlias alias="Section" type="domain.blog.Section"/>
  <typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>

当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。

也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

<typeAliases>
  <package name="domain.blog"/>
</typeAliases>

每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。见下面的例子:

@Alias("author")
public class Author {
    ...
}

MybatisPlus

注意

SpringBoot升级3.2报错Invalid value type for attribute ‘factoryBeanObjectType‘: java.lang.String的解决方案

mybatis-spring 官方 ISSUE 说明在 3.0.3 版本修复此问题

Mybatis-Plus 官方 ISSUE#5808 下面也说明会在 3.5.5 版本升级 mybatis-spring 依赖修复此问题,但截止到目前只有快照版本 3.5.5-SNAPSHOT

所以目前好一点的方案就是手动升级 mybatis-spring 版本为 3.0.3

<dependency>
	<groupId>com.baomidou</groupId>
	<artifactId>mybatis-plus-boot-starter</artifactId>
	<version>3.5.7</version>
	<exclusions>
		<exclusion>
			<artifactId>mybatis-spring</artifactId>
			<groupId>org.mybatis</groupId>
		</exclusion>
	</exclusions>
</dependency>
<dependency>
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis-spring</artifactId>
	<version>3.0.3</version>
</dependency>

常见注解

MyBatisPlus通过扫描实体类,并基于反射获取实体类信息作为数据库表信息。

  • 类名驼峰转下划线作为表名

  • 名为id的字段作为主键

  • 变量名驼峰转下划线作为表的字段名

MybatisPlus中比较常用的几个注解如下::

  • @TableName:用来指定表名
  • @Tableld:用来指定表中的主键字段信息
  • @TableField:用来指定表中的普通字段信息

Tableld的type属性中是ldType枚举

ldType
AUTO:数据库自增长
INPUT:通过set方法自行输入
ASSIGN_ID:分配ID·接口ldentifierGenerator的方法nextld来生成id 默认实现类为DefaultldentifierGenerator雪花算法

使用@TableField的常见场累:

  • 成员变量名与教据库字段名不一致(设置value)
  • 成员变量名以is开头,且是布尔值(设置value)
  • 成员变量名与教据库关键词冲突(比如order字段注解为 @TableField(“order”) )
  • 成员变量不是数据库字段 @TableField(exist=false)

常见配置

MyBatisPlus的配置项继承了MvBatis原生配置和一些自己特有的配置。例如:

yaml
mybatis-plus:
  type-aliases-package: fun.maluyao.pojo #别名扫描包
  mapper-locations: "classpath*:/mapper/**/*.xml" #Mapper.xml文件地址,默认值 configuration:
  configuration:
    map-underscore-to-camel-case: true #是否开启下划线和驼峰的映射
    cache-enabled: false #是否开启二级缓存
  global-config:
    db-config:
      id-type: assign_id #id为雪花算法生成
      update-strategy: not_null #更新策略:只更新非空字段

条件构造器Wapper

new QueryWrapper<Main>();
new UpdateWrapper<Main>();
new LambdaQueryWrapper<>();
new LambdaUpdateWrapper<>();

自定义SQL

我们可以利用MyBatisPlus的Wrapper来构建复杂的Where条件,然后自己定义SQL语句中剩下的部分

1、基于Wrapper构建where条件

List<Long> ids = List.of(1L,2L, 4L):
int amount=200;
//1.构建条件
LambdaQuerywrapper<User> wrapper=new LambdaQuerywrapper<User>().in(User::getId,ids); //2.自定义SQL方法调用
userMapper.updateBalanceByIds(wrapper,amount);

2、在mapper方法参数中用Param注解声明wrapper变量名称,必须是ew

void updateBalanceByIds(@Param("ew")LambdaQuerywrapper<User>wrapper,@Param("amount")int amount);

3、自定义SQL,并使用Wrapper条件

<update id="updateBalanceByIds">
	UPDATE tb_user sET balance=balance-#{amount} ${ew.customSqlSegment}
</update>

IService接口和 ServiceImpl实现

public class MainServiceImpl extends ServiceImpl<MainMapper,Main> implements MainService{
}

ServiceImpl两个泛型,前面那个是mapper,后面那个是实体类,可以直接获取到baseMapper

lambdaQuery的动态查询:

​ 在使用wrapper构建条件查询的时候可以填入三个参数,第一个参数作为动态SQL的条件

IService的Lambda查询

在IService的实例中可以使用LambdaQuery,他可以返回一个wrapper

BaseMapper

这个定义好了单表的mapper操作

逻辑删除

逻辑删除就是基于代码逻辑模拟删除效果,但并不会真正删除数据。思路如下

  • 在表中添加一个字段标记数据是否被删除
  • 当删除数据时把标记置为1
  • 查询时只查询标记为0的数据

MybatisPlus提供了逻辑删除功能,无需改变方法调用的方式,而是在底层帮我们自动修改CRUD的语句。我们要做的就是在application.yaml文件中配置逻辑删除的字段名称和值即可:

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field:flag#全局逻辑删除的实体字段名,字段类型可以是boolean、integer
      logic-delete-value:#逻辑已删除值(默认为1)
      logic-not-delete-value: #逻辑未删除值(默认为0)

枚举处理器

@Getter
@AllArgsConstructor
@NoArgsConstructor
public enum Season {
    SPRING(100,"春天"),
    SUMMER(200,"夏天"),
    AUTUMN(300,"秋天"),
    WINTER(400,"冬天")
    ;
    @EnumValue
    Integer code;
    String desc;
}

使用@EnumValue注解可以标记属性为与数据库链接的属性,即写入数据库使用这个属性

是配置生效

mybatis-plus:
  configuration:
    default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler

修改枚举的JSON处理

修改Jacson处理使用@JsonValue注解

@JsonValue
String desc;

JSON处理

在实体类属性上加上TableField注解的typeHandler属性指定使用JacksonTypeHandler来处理JSON数据

@TableField(typeHandler =JacksonTypeHandler.class) 
privaate UserInfo info;

对象嵌套需要定义复杂的result map(mybatis中的)

我们可以使用TableName注解的autoResultMap属性自动配置

@TableName(autoResultMap = true)

Page插件

加载插件

@SpringBootConfiguration
public class ApplicationConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptorFactory()  {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
        paginationInnerInterceptor.setMaxLimit(200L);
        interceptor.addInnerInterceptor(paginationInnerInterceptor);
        return interceptor;
    }
}

使用分页:(在service层就有page方法能使用page)

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class);
        Page<Main> page = new Page<>(1,2);
        IPage<Main> paged = Db.page(page, Main.class);
        paged.getRecords().forEach(System.out::println);
    }
}

开启二级缓存

全局配置

mybatis-plus:  
  configuration:    
    cache-enabled: true

在 XxxMapper.java 上添加 @CacheNamespace 注解

package com.xxx.myProject.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.CacheNamespace;

@CacheNamespace
public interface SysUserMapper extends BaseMapper<SysUser> {
	// 省略
}
缓存对象要实现 Serializable 接口
java
// 其它省略
public class SysUser implements Serializable {
	// 省略
}

(可选)在 XxxMapper.xml 上添加 ,并指定命名空间 如果项目中使用 Mapper.xml 写 SQL ,则配置如下, 如果没有,则跳过此步骤。

<cache-ref namespace="com.xxx.myProject.mapper.SysUserMapper"   />

此处为什么使用 <cache-ref /> ,而不是 <cache /> ?

如果 使用 <cache /> ,即 <cache namespace="com.xxx.myProject.mapper.SysUserMapper" /> ,则 项目中出现两个key相同的缓存 ,即 XxxMapper.xml 中的缓存和 XxxMapper.java 中的缓存, 这两个缓存 肯定不会合并,谁覆盖谁呢 ? 实际上,MybatisPlus 内置很多常用方法,另外,开发者还可以在 XxxMapper.java 或者 XxxMapper.xml 自定义SQL, 这两处的SQL 都是 XxxMapper 下方法,对应的缓存 应该合并到一起, 因此应该使用 <cache-ref /> ,让 XxxMapper.xml 中的缓存 合并到 XxxMapper.java 的缓存中 。

启动类添加开启二级缓存注解

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
@EnableCaching
//不加也能运行
public class AmsConfigApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(AmsConfigApplication.class, args);
    }
}

一级缓存基于SqlSession,

二级缓存基于namespace,同一个namespace下所有数据可以共享

spring boot 中使用mybatis plus的二级缓存缓存_mybatis

1.Mybatis的缓存

缓存是一般的ORM框架都会提供的功能,目的就是提升查询的效率和减少数据库的压力。跟Hibernate一样,Mybatis也有一级缓存和二级缓存,并且预留了集成第三方缓存的接口。

特点:缓存数据不可靠(可能会丢失),存放热点数据(经常使用的数据)

优点:缓存大部分存在内存中,查询速度快

  • 一级缓存基于SqlSession,
  • 二级缓存基于namespace,同一个namespace下所有数据可以共享

在这篇文章中介绍配置文件时曾说明namespace与二级缓存相关

一个mapper.xml用自己的namespace,另外也可以多个mapper(dao接口)共享同一个二级缓存namespace,即namespace取名可以相同

一级缓存与二级缓存调用顺序:

如果有得到,写入(更新)一级缓存

若没有得到,再去一级缓存,如果仍没有,再去数据库

优先级: 二级缓存--->一级缓存--->数据库

2.一级缓存 一级缓存:基于SqlSession,默认就是开启的。

特点:

查询第一次时,获取到数据写入一级缓存,再次查询时从缓存获取,不再执行sql语句 若当前SqlSession发生修改、增加、删除动作时,就会立即把当前缓存的所有数据清空 对于查询操作,只要SqlSession没有调用flush或者close方法,它就一直存在

@Test //一级缓存
    public void firstLevelCacheTest(){
        //查询所有时 可以缓存数据 但是一旦发生增加 或者删除 修改 就要清空
        iStudentDao.findAllStudentWithScoreLazy();

        //查询第一次 有执行sql select * from student_tb where id = ?
        Student student1 = iStudentDao.findStudentById(2);
        System.out.println("student:" + student1);

        //查询第二次 没有执行sql 没有去数据库查数据 从一级缓存sqlSession获取数据
        Student student2 = iStudentDao.findStudentById(2);
        System.out.println("student:" + student2);

        student2.setAge(22);
        int num = iStudentDao.upateStudent(student2);
        System.out.println("num:"+num);

        //再次查询数据 需要去数据库查数据 为什么??
        //应为 当前sqlSession 发生 修改、删除、增加动作时,就会把当前缓存的所有数据清空
        Student student3 = iStudentDao.findStudentById(2);
        System.out.println("student3:"+student3);

        iStudentDao.findAllStudentWithScoreLazy();
    }

3.二级缓存 二级缓存是mapper映射级别的缓存,多个SqlSession去操作同一个mapper映射的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。(也可以说是,在同一namespace下,共享一块缓存空间,如果多个mapper (dao.xml)共享同一namesapce 则也共享一块缓存,二级缓存是跨sqlsession,多个sqlsession可以去二级缓存获取数据。即可以针对同一个dao接口或者同一个命名空间(namespace)创建多个SqlSession )

特点:

  • 只要发生增删改,就会将·同一命名空间(namespace)下的缓存清空
  • 使用二级缓存实体类必须实现序列化,否则报错
  • 使用查询语句,默认只写入一级缓存,只有调用close(),commit()方法,才会将数据提交到二级缓存,其他的sqlsession才能拿到,不再执行sql语句

3.1实现 1.开启二级缓存(mybatis配置文件中开启)

<settings>
        <!--开启二级缓存-->
        <setting name="cacheEnabled" value="true"/>
    </settings>

2.在mapper的命名空间下配置缓存(xxx.xml)

<!--配置缓存
        1.开启<cache></cache>
        2.配置参数
        - flushinterval` 缓存刷新间隔,缓存多长时间刷新一次,默认不清空,设置一个毫秒值
- `readOnly`: 是否只读;**true 只读**,MyBatis 认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。MyBatis 为了加快获取数据,直接就会将数据在缓存中的引用交给用户。不安全,速度快。**读写(默认)**:MyBatis 觉得数据可能会被修改
- `size` : 缓存存放多少个元素
- `type`: 指定自定义缓存的全类名(实现Cache 接口即可)
- `blocking`: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。
    -->
   <cache flushInterval="10000" eviction="LRU" size="1000" readOnly="false" blocking="false"></cache>

eviction:缓存回收策略,有如下几种回收策略:

  • LRU(常用,默认) -最近最少回收,移除最长时间不被使用的对象
  • FIFO(不常用)-先进先出,按照缓存进入的顺序来移除他们
  • SOFT(了解)-软引用,移除基于垃圾回收器状态和软引用规则的对象
  • WEAK(了解)-弱引用,更积极的移除基于垃圾收集器和弱引用规则的对象
  • 默认是 LRU 最近最少回收策略

flushinterval 缓存刷新间隔,缓存多长时间刷新一次,默认不清空,设置一个毫秒值 readOnly: 是否只读;true 只读,MyBatis 认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。MyBatis 为了加快获取数据,直接就会将数据在缓存中的引用交给用户。不安全,速度快。读写(默认):MyBatis 觉得数据可能会被修改 size : 缓存存放多少个元素 type: 指定自定义缓存的全类名(实现Cache 接口即可) blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。

3.使用

对于sql语句默认就开启了,不用另外配置,也可在此关闭缓存,这一条sql不使用不缓存

<!--
        useCache="true" 使用缓存 一般不需要配置 默认就开启true        
    -->
    <select id="findStudentById" resultType="Student" useCache="true" >
        select * from  student_tb where id = #{id}
    </select>

4.测试

@Test  //二级缓存
    public void secondLevelCacheTest(){
        //查询第一次  执行sql语句select * from student_tb where id = ?
        Student student1 = iStudentDao.findStudentById(2);
        System.out.println("student:" + student1);

        //查询第二次,没有执行sql 从一级缓存sqlSession中获取,因为二级缓存中没有
        Student student2 = iStudentDao.findStudentById(2);
        System.out.println("student2:"+student2);

        //如果不执行这句话  下面的代码仍会执行sql
        //调用close(),commit()方法 才会将数据提交到二级缓存,其他的sqlsession才能拿到
        //sqlSession.close();
        sqlSession.commit();

        // studentDao2   studentDao 来自于不同的sqlSession
        iStudentDao2 = sqlSessionFactory.openSession(true).getMapper(IStudentDao.class);
        Student student3 = iStudentDao2.findStudentById(2);
        System.out.println("student3:"+student3);

    }

4.一级缓存与二级缓存的调用顺序与区别(重要)

  • 一级缓存 在sqlSession 中
  • 二级缓存 多个sqlSession 可以二级缓存 二级缓存是以namesapce 进行划分 多个mapper 可以共享一个二级缓存

调用顺序:先查二级缓存,再查一级缓存,最后查数据库

最简单的真测试:

开启数据库访问日志:

yml
logging:
  level:
    top.maluyao.mapper: DEBUG

然后看使用的查询语句就知道了

共同点:查询缓存数据 , 只要发生增删改 立即将 一级二级全部清除

spring boot 中使用mybatis plus的二级缓存缓存 https://blog.51cto.com/u_16099358/12270014

评论

评论加载中……