MyBatis

MyBatis

环境:

  • JDK 1.8

  • Mysql 8.0.13

  • maven 3.6.2

  • IDEA 2021.1

回顾:

  • JDBC

  • mysql

  • Java基础

  • maven

  • junit

框架:配置文件,看官网

一、简介

1.1 什么是MyBatis?

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

MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。

MyBatis 可以通过简单的 XML注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

MyBatis的前身是apache的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis。2013年11月迁移到Github
image

1.2 获取MyBatis

  • maven
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
</dependency>

1.3 持久化

数据持久化

  • 持久化就是将程序的数据在持久状态和瞬时状态转化的过程

  • 内存:断电即失

  • 数据库,io文件持久化

  • 生活:冷藏、罐头

为什么需要持久化?

  • 有一些对象,不能让他丢掉

  • 内存太贵了

1.4 持久层

Dao层、service层、Controller层...

  • 完成持久化工作的代码块

  • 层界限十分明显

1.5 为什么需要MyBatis?

  • 帮助开发人员将数据存入到数据库中

  • 方便

  • 传统的jdbc代码太复杂,简化、框架、自动化

  • 更重要的一点:使用的人多

二、CRUD

  1. namespace: namespace是接口(dao/mapper)的全路径名

  2. select

  • id : 就是接口中的方法名

  • resultType : sql语句执行的返回值

  • parameterType : 参数类型

  1. insert

  2. update

  3. delete

注意点:增删改需要提交事务!

三、配置解析

3.1 核心配置文件

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:

3.2 环境配置environments

  • 可以配置多套环境

  • 默认使用的环境 ID(比如:default="development")

  • 每个 environment 元素定义的环境 ID(比如:id="test")

  • 事务管理器的配置(比如:type="JDBC")

    • 事务管理器(transactionManager)

    • 在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):

      • JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。

      • JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。

  • 数据源的配置(比如:type="POOLED")

    • 数据源(dataSource)

    • dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。

    • 有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]")

      • UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。

      • POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。

      • JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8"/>
            <property name="username" value="root"/>
            <property name="password" value="root"/>
        </dataSource>
    </environment>

    <environment id="test">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8"/>
            <property name="username" value="root"/>
            <property name="password" value="root"/>
        </dataSource>
    </environment>
</environments>

3.3 属性properties

  1. 编写一个配置文件:jdbc.properties
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/test
jdbc.username=root
jdbc.password=root
  1. 在核心配置文件中引入jdbc.properties
<!--引入外部配置文件-->
<properties resource="jdbc.properties"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</properties>
  • 可以直接引入外部文件

  • 可以在其中增加一些属性配置

  • 如果两个文件有同一个字段,优先加载使用外部配置文件的!

注意点: properties中还可以再写property属性,如果jdbc.properties和mybatis-config.xml都有username字段值,优先加载jdbc.properties文件中的值!

3.4 类型别名typeAliases

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

  • 方式一:为每一个实体类设置别名

mybatis-config.xml

<typeAliases>
     <typeAlias type="cn.com.longer.entity.User" alias="userAlias"></typeAlias>
</typeAliases>

UserMapper.xml

<update id="updateUser" parameterType="userAlias">
        update user set pwd = #{pwd} where id = #{id}
</update>
  • 方式二:为实体类所在的包设置

mybatis-config.xml

<typeAliases>
<!--        <typeAlias type="cn.com.longer.entity.User" alias="userAlias"></typeAlias>-->
        <package name="cn.com.longer.entity"/>
    </typeAliases>

UserMapper.xml

<update id="updateUser" parameterType="user">
        update user set pwd = #{pwd} where id = #{id}
 </update>

注: 为实体包设置别名,xml文件中paramterType应该为实体类类名首字母小写!

  • 方式三:在实体类上添加注解

    @Alias("userAlias")
    public class User {}
    

3.5 设置settings

这是mybatis中极为重要的调整设置,它会改变MyBatis的运行时行为。

image
image
image
image

一个配置完整的 settings 元素的示例如下:

<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
  <setting name="useGeneratedKeys" value="false"/>
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <setting name="defaultStatementTimeout" value="25"/>
  <setting name="defaultFetchSize" value="100"/>
  <setting name="safeRowBoundsEnabled" value="false"/>
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

3.6 映射器mapper

MapperRegistry : 注册绑定我们的mapper文件!

org.apache.ibatis.binding.BindingException: Type interface cn.com.longer.dao.UserMapper is not known to the MapperRegistry.
  • 方式一:使用mapper.xml文件进行绑定【推荐使用】
<!--  每一个mapper.xml都需要在mybatis核心配置文件中注册!  -->
    <mappers>
        <mapper resource="cn/com/longer/dao/UserMapper.xml"/>
    </mappers>
  • 方式二:使用class文件进行绑定
<mappers>
    <mapper class="cn.com.longer.dao.UserMapper"></mapper>
</mappers>

注意点:

  • 接口和它的mapper配置文件必须同名!

  • 接口和它的mapper配置文件必须在同一个包下!
    image

  • 方式三:使用扫描package进行注册

<mappers>
    <package name="cn.com.longer.dao"/>
</mappers>

注意点:

  • 接口和它的mapper配置文件必须同名!

  • 接口和它的mapper配置文件必须在同一个包下!

3.7 生命周期和作用域

作用域生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。
image

SqlSessionFactoryBuilder

  • 一旦用SqlSessionFactoryBuilder创建完SqlSessionFactory后,就不再需要它了

  • 它的最佳作用域是方法作用域(也就是局部方法变量

SqlSessionFactory

  • 可以把它想象为:数据库连接池

  • SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在没有任何理由丢弃它或重新创建另一个实例。

  • 因此 SqlSessionFactory 的最佳作用域是应用作用域。使用单例模式或者静态单例模式。

SqlSession

  • 连接到请求池的一个请求

  • 每个线程都应该有它自己的 SqlSession 实例。

  • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。

  • 每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。用完之后赶紧关闭,否则资源被占用!把这个关闭操作放到 finally 块中。

Mapper映射器实例

  • 映射器接口的实例是从 SqlSession 中获得的。

  • 映射器实例应该在调用它们的方法中被获取,使用完毕之后即可丢弃。
    image

四、结果集映射

问题:Java实体类中的属性名和数据库表中的字段名不一致!

User.java | id | name | password
user表    | id | name | pwd
  • 方法一:sql别名

    <select id="getUserList" resultType="cn.com.longer.entity.User">
       select id, name, pwd as password from user
    </select>
    
  • 方法二:resultMap

    结果集映射:

    <resultMap id="BaseUserMap" type="cn.com.longer.entity.User">
        <result column="id" property="id"></result>
        <result column="name" property="name"></result>
        <result column="pwd" property="password"></result>
    </resultMap>
    <select id="getUserList" resultMap="BaseUserMap">
        select id, name, pwd from user
    </select>
    
  • 注:如果column和property一致时,不需要配置它们,只需要配置二者不一样的即可!

  • resultMap 元素是 MyBatis 中最重要最强大的元素。

  • ResultMap 的优秀之处——你完全可以不用显式地配置它们。

  • 如果这个世界总是这么简单就好了。

五、日志

5.1 日志工厂

settings设置中有此设置
image

logImpl:

  • SLF4J

  • LOG4J(deprecated since 3.5.9) 【掌握】

  • LOG4J2

  • JDK_LOGGING

  • COMMONS_LOGGING

  • STDOUT_LOGGING 【掌握:标准日志工厂实现】

  • NO_LOGGING

<setting name="logImpl" value="STDOUT_LOGGING" />

image

5.2 Log4j

什么是Log4j?

  • Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件

  • 也可以控制每一条日志的输出格式

  • 通过定义每一条日志信息的级别(info、debug、error),我们能够更加细致地控制日志的生成过程

  • 这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码

使用步骤:

  1. 导包
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
  1. mybatis-config.xml中设置日志的具体实现
<settings>
    <setting name="logImpl" value="LOG4J"/>
</settings>
  1. 创建log4j.properties配置文件
# 将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码中
log4j.rootLogger=DEBUG,console,file

# 控制台输出的相关设置
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Target=System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n

# 文件输出的相关设置
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/longer.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

# 日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
  1. 在要输出日志的类中加入相关语句
public class UserMapperTest {
    // 定义属性
    static Logger logger = Logger.getLogger(UserMapperTest.class);

    @Test
    public void getUserList(){
        logger.info("logger.info");
        logger.debug("logger.debug");
        logger.error("logger.error");
    }
}

六、分页

为什么要分页?

减少数据的处理量

  1. 方式一:limit
-- 语法:SELECT * FROM table_name LIMIT startIndex, pageSize;
SELECT * FROM user LIMIT 3; [0, n]
  1. 方式二:使用MyBatis实现分页,核心SQL

  2. 接口

List<User> getUserListByLimit(HashMap<String, Integer> limitMap);
  1. mapper.xml
<select id="getUserListByLimit" parameterType="map" resultMap="BaseUserMap">
    select * from user limit #{startIndex}, #{pageSize}
</select>

3.测试

@Test
    public void getUserListByLimit(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        HashMap<String, Integer> map = new HashMap<>();
        map.put("startIndex", 1);
        map.put("pageSize", 2);
        List<User> userList = mapper.getUserListByLimit(map);
        for (User user : userList) {
            logger.info("getUserListByLimit:" + user);
        }
        sqlSession.close();
    }
  1. 分页插件

pageHelper插件: https://pagehelper.github.io/

七、使用注解开发

MyBatis的sql也可以使用注解的形式接在接口方法上面!

使用步骤:

  1. mybatis-config.xml进行接口注册
<mappers>
    <mapper class="cn.com.longer.dao.UserMapper"></mapper>
</mappers>
  1. 在接口中的方法写sql
@Select("select * from user")
List<User> selectUserList();

@Insert("insert into user(id, name, pwd) values(#{id}, #{name}, #{password})")
int insertUser(User user);

@Delete("delete from user where id = #{uid}")
int deleteUser(@Param("uid") int id);

@Update("update user set name = #{name} and pwd = #{password} where id = #{id}")
int updateUser(User user);

本质: 反射机制实现

底层: 动态代理

注解的局限性:

  • 只适用于简单的sql

  • 如果是复杂的sql,数据库表字段与实体类属性不一致,则力不从心

关于@Param()注解:

  • 基本类型的参数或者String类型,需要加上

  • 引用类型不需要加

  • 如果只有一个基本类型的参数时,可以不加,但建议加上

  • sql语句中的运用就是这里@Param()中设定的属性名

八、Lombok

Lombok是作用在POJO的,极大的简化了代码量,避免程序员写许多重复的代码!

使用步骤:

1.IDEA中安装Lombok插件

2.引入lombok的pom依赖

<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.22</version>
    </dependency>
</dependencies>
  1. 实体类中使用注解
@Data : NoArgsConstructor, Getter,Setter,equals,hashCode,toString
@Getter and @Setter
@FieldNameConstants
@ToString
@EqualsAndHashCode
@AllArgsConstructor, 
@RequiredArgsConstructor  
@NoArgsConstructor

九、MyBatis详细的执行流程

  1. Resource获取加载全局配置文件(mybatis-config.xml)

  2. 实例化SqlSessionFactoryBuilder构造器

  3. 解析配置文件流XMLConfigBuilder

  4. Configuration所有的配置信息

  5. SqlSessionFactory实例化

  6. transaction事务管理

  7. 创建executor执行器

  8. 创建SqlSession

  9. 实现CRUD

  10. 查看是否执行成功

  11. 提交事务

  12. 关闭

十、多对一的处理 :

多个学生对应一个老师:

10.1 根据查询嵌套处理

数据库表:

-- student表
id name tid
-- teacher表
id name

Java实体类:

public class Student{
    private int id;
    private String name;
    private Teacher teacher;   
}
public class Teacher{
    private int id;
    private String name;
}

1.接口

List<Student> getStudent();

2.Mapper.xml

<resultMap id="StudentTeacher" type="cn.com.longer.entity.Student"> 
    <result property="id" column="id" />
    <result property="name" column="name" />
    <!--复杂的属性,我们需要单独处理。 对象:association 集合:collection-->
    <association property="teacher" column="tid" javaType="cn.com.longer.entity.Teacher" select="getTeacher" />
</resultMap>
<select id="getStudent" resultMap="StudentTeacher">
    select * from student
</select>

<select id="getTeacher" resultType="cn.com.longer.entity.Teacher">
    select * from teacher where id = #{id}
</select>

10.2 按照结果嵌套处理

  1. 接口
List<Student> getStudent2();
  1. Mapper.xml
<resultlMap id="StudentTeacher2" type="cn.com.longer.entity.Student">
    <result property="id" column="sid" />
    <result property="name" column="sname" />
    <association property="teacher" javaType="cn.com.longer.entity.Teahcer">
        <result property="id" column="tid" />
        <result property="name" column="tname" />
    </association>
</resultMap>

<select id="getStudent2" resultMap="StudentTeacher2">
    select s.id sid, s.name sname, t.id tid, t.name tname
    from student s, teacher t
    where s.tid = t.id
</select>

十一、一对多处理

比如:老师拥有多个学生,对于老师而言,就是一对多的关系!

获取指定老师下的所有学生及老师的信息:

11.1 按照结果嵌套处理

Java实体类

public class Student{
    private int id;
    private String name;
}
public class Teacher{
    private int id;
    private String name;
    private List<Student> students;
}
  1. 接口:
Teacher getTeacherById(@Prarm("tid") int id);
  1. Mapper.xml
<resultMap id="teachetStudentList" type="cn.com.longer.entity.Teacher">
    <result property="id" column="tid" />
    <result property="name" column="tname" />
    <!--复杂的属性,我们需要单独处理: 对象:association 集合:collection
        javaType 指定属性的类型
        集合中的泛型信息,我们使用ofType获取
    -->
    <collection property="students" ofType="cn.com.longer.entity.Student">
        <result property="id" column="sid" />
        <result property="name" column="sname" />
    </association>
</resultMap>

<select id="getTeacherById" resultMap="teachetStudentList">
    select s.id sid, s.name sname, t.id tid, t.name tname
    from student s, teacher t
    where s.tid = t.id and t.id = #{tid}
</select>

11.2 按照查询嵌套处理

1.接口:

Teacher getTeacherById2(@Param("tid") int id);

2.Mapper.xml

<resultMap id="teachetStudentList2" type="cn.com.longer.entity.Teacher">
    <!--属性和字段相同的可以不写-->
    <collection property="students" column="id" javaType="ArrayList" ofType="cn.com.longer.entity.Student" select="getStudentByTeacherId" />
</resultMap>

<select id="getTeacherById2" resultMap="teachetStudentList2">
    select id, name from teacher where id = #{tid}
</select>
<select id="getStudentByTeacherId" resultType="cn.com.longer.entity.Student">
    select * from student where tid=#{tid}
</select>

小结:

  1. 关联 - association [多对一]

  2. 结合 - collection [一对多]

  3. javaType & ofType

  4. javaType : 用来指定实体类中属性的类型

  5. ofType : 用来指定映射到List或者集合中的pojo类型,泛型中的约束类型

注意点:

  • 保证SQL的可读性,计量保证通俗易懂
  • 注意一对多和多对一中,属性名和字段的问题
  • 如果问题不好排查错误,可以使用日志,建议使用Log4j

面试高频:

  • MySQL引擎
  • InnoDB底层原理
  • 索引
  • 索引优化

十二、动态SQL

什么是动态SQL? 根据不同的条件,生产不同的SQL

12.1 SQL片段

有些时候,我们需要将一些功能的sql抽取出来,方便复用!

  1. 使用sql标签抽取公共部分:
<sql id="if-title-author">
    <if test="title !=nulll">
        title = #{title}
    </if>
    <if test="author !=nulll">
        and author = #{author}
    </if>
</sql>
  1. 在需要使用的地方使用include 标签引用即可:
<select id="queryBlogIf" parameterType="map" resultType="blog">
    select * from blog
    <where>
        <include refid="if-title-author" />
    </where>
</select>

注意事项:

  • 最好基于单表来定义SQL片段

  • 不要存在where标签

12.2 if

使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。

<select id="findActiveBlogLike" resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>

12.3 choose (when, otherwise)

  • 有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

  • 还是上面的例子,但是策略变为:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回标记为 featured 的 BLOG

<select id="findActiveBlogLike" resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

12.4 trim (where, set)

  • where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
<select id="findActiveBlogLike" resultType="Blog">
  SELECT * FROM BLOG
  <where>
    <if test="state != null">
         state = #{state}
    </if>
    <if test="title != null">
        AND title like #{title}
    </if>
    <if test="author != null and author.name != null">
        AND author_name like #{author.name}
    </if>
  </where>
</select>
  • 如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

prefixOverrides: 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。

  • 用于动态更新语句的类似解决方案叫做 setset 元素可以用于动态包含需要更新的列,忽略其它不更新的列。
<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
      <if test="email != null">email=#{email},</if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>

set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。

  • 来看看与 set 元素等价的自定义 trim 元素吧:
<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

这样:我们覆盖了后缀值设置,并且自定义了前缀值。

3.5 Foreach

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。

foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!

  • 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach

  • 当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。

  • 当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

使用数组:

  1. 接口
List<Post> selectPostIn(List<Integer> ids);

2.Mapper.xml

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

使用map:

  1. 接口
List<Blog> queryBlogForeach(Map map);
  1. Mapper.xml
<!--传递一个万能的Map,map中可以存在一个集合!-->
<select>
    select * from blog
    <where>
        <foreach collection="ids" item="id" open="and (" close=")" separator="or">
            id=#{id}
        </foreach>
    </where>
</select>
  1. 测试
Map map = new HashMap();
List ids = new ArrayList<Integer>();
ids.add(1);
ids.add(2);
map.put("ids", ids);
List<Blog> blogList = mapper.queryBlogForeach(map);

十三、缓存

13.1 简介:

查询 : 连接数据库,耗资源!
    一次查询的结果,给他暂存在一个可以直接取到的地方!---> 内存:缓存
    我们再次查询相同数据的时候,直接走缓存,就不用走数据库了!
  1. 什么是缓存[cache]?
  • 存在内存中的临时数据

  • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。

  1. 为什么使用缓存?
  • 减少和数据库的交互次数,减少系统开销,提高系统效率。

  • 缓存数据在内存,内存的读取速度 > 硬盘

  1. 什么样的数据能使用缓存?
  • 经常查询并且不经常改变的数据。【可以使用缓存】

13.2 MyBatis缓存

  • MyBatis包含了一个非常强大的查询缓存特性,它可以非常方便地制定和配置缓存。缓存可以极大的提升查询效率。

  • MyBatis系统中默认定义了两级缓存:一级缓存二级缓存

    • 一级缓存:SqlSession级别的缓存,也称本地缓存。默认情况下,只有一级缓存开启

    • 二级缓存需要手动开启和配置,它是基于namespace级别的缓存(mapper.xml文件)

    • 为了提高扩展性,MyBatis定义了缓存接口Cacha。我们可以通过实现Cache接口来自定义二级缓存。

13.3 一级缓存

  • 一级缓存也叫本地缓存:

    • 与数据库同一次会话期间查询到的数据会放在本地缓存中

    • 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库

测试步骤:

  1. 开启日志

  2. 测试在一个SqlSession中查询两次相同的记录

  3. 查看日志输出

缓存失效:

  1. 查询不同的数据

  2. 增删改操作,可能会改变原来的数据,所以必定会刷新缓存

  3. 查询不同的Mapper.xml

  4. 手动清除缓存:

sqlSession.clearCache(); // 手动清理缓存

小结:

  • 一级缓存默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区间段

  • 一级缓存相当于一个Map

13.4 二级缓存

  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存

  • 基于namespace基本的缓存,一个命名空间,对应一个二级缓存

  • 被缓存的实体类需要序列化 User implements Serializable

  • 若某个SQL语句不想使用缓存 useCache="false" or flushCache='true'

  • 工作机制:

    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中

    • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中

    • 新的会话查询信息,就可以从二级缓存中获取内容

    • 不同的mapper查出的数据会放在自己对应的缓存(map)中

使用步骤:

  1. mybatis-config.xml中开启全局缓存
<!--显示的开启全局缓存-->
<setting name="cacheEnabled" value="true" />
  1. 在要使用二级缓存的Mapper.xml文件中,添加标签
<!--在当前mapper.xml中使用二级缓存-->
<cache 
    eviction="FIFO"
    flushInterval="60000"
    size="512"
    readOnly="true"
/>

小结:

  • 只要开启了二级缓存,在同一个Mapper下就有效

  • 所有的数据都会先放在一级缓存中

  • 只有当会话提交,或者关闭时,才会提交到二级缓存中

13.5 缓存原理

缓存顺序:

  1. 先看二级缓存中有没有

  2. 再看一级缓存中有没有

  3. 查询数据库

13.6 自定义缓存-ehcache

ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存!

使用步骤:

  1. 导包
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.1.0</version>
</dependency>
  1. 在Mapper.xml中指定使用我们的ehcache,实现缓存
<cache type="org.mybatis.caches.ehcache.EhcacheCache" />

Redis数据库来做缓存!K-V

13.7 引用缓存 cache-ref

对某一命名空间的语句,只会使用该命名空间的缓存进行缓存或刷新。 但你可能会想要在多个命名空间中共享相同的缓存配置和实例。要实现这种需求,你可以使用 cache-ref 元素来引用另一个缓存。

<cache-ref namespace="com.someone.application.data.SomeMapper"/>
posted @   zlonger  阅读(29)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示