Mybatis——进阶(重点:一级缓存和二级缓存)

目录

1、环境搭建

2、parameterType

1.1、传入多个参数

1.2、解决方式1

1.3、解决方式2

3、#与$的区别

3.1、#的用法

3.2、$的用法

3.3、#和$的区别

4、resultMap用法

5、sql片段

5.1、用法1

5.2、用法2

6、动态sql

6.1、动态sql-if标签

6.2、动态sql-choose,when,otherwise标签

6.3、动态sql-where标签

6.4、动态sql-set标签

6.5、动态sql-foreach标签

6.6、动态sql-trim标签

7、缓存(面试重点)

7.1、一级缓存

7.2、二级缓存

7.3、开启二级缓存的三种方式

7.4、测试二级缓存


前言:请先行观看之前文章Mybatis——快速入门篇后再看该篇文章,方便理解

1、环境搭建

数据库

CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(20) DEFAULT NULL,
  `password` varchar(20) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `sex` char(1) DEFAULT NULL,
  `email` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8;

pom.xml文件

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.itssl</groupId>
    <artifactId>mybatis_day02</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.18</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.9</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>
    </dependencies>


</project>

User实体类

@Data
public class User {
    private int id;
    private String username;
    private String password;
    private int age;
    private String sex;
    private String email;
}

jdbc.properties 

driver=com.mysql.jdbc.Driver
url=jdbc:mysql:///数据库名
username=root
password=你的数据库密码

mybatis-config.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>
    <!--引入properties文件,此时就可以${属性名}的方式访问属性值-->
    <properties resource="jdbc.properties"></properties>
    <typeAliases>
        <package name="cn.itssl.pojo"/>
    </typeAliases>
    <!--设置连接数据库的环境-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url"
                          value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--引入映射文件-->
    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>
</configuration>

2、parameterType

1.1、传入多个参数

UserMapper接口

public interface UserMapper {
    /**
     * @description:用户登录
     **/
    User loginUser(String username,String password);
}

UserMapper.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 namespace="cn.itssl.mapper.UserMapper">
    <!--用户登录-->
    <select id="loginUser" resultType="User">
        select *
        from t_user
        where username = #{username}
          and password = #{password}
    </select>
</mapper>

选中UserMapper接口,点击右键,选中Go To,选择Test 

选择setUp/@Before,在下面选中方法,因为我这里idea出问题了,没有提示出来,所以没有显示

 

 

idea会自动生成一个测试类存放在test包下

编写代码

public class UserMapperTest {
    private SqlSession sqlSession;
    private UserMapper userMapper;
    @Before
    public void setUp() throws Exception {
        //获取核心配置文件
        String resource = "mybatis-config.xml";
        //加载配置文件
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //构建SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //构建Sqlsession对象
        sqlSession = sqlSessionFactory.openSession(true);
        //把sqlSession对象传递至dao层
        // userDao = new UserDaoImpl(sqlSession);
        //使用动态代理
        userMapper = sqlSession.getMapper(UserMapper.class);
    }
    @Test
    public void login(){
        User user = userMapper.loginUser("jack", "1234");
        System.out.println(user);
    }
}

运行login方法

发现出现了异常,那么肯定是我们代码有地方书写的有问题,因为Mybatis在多参数传递时,需要进行解决异常,那么下面提供两种解决方式

1.2、解决方式1

在mapper.xml中使用 0,1这样的序号去,0表示第一个参数,1表示第二个参数。(从0开始数)

    <!--登录-->
    <select id="loginUser" resultType="User">
        select * from t_user where username = #{arg0} and password = #{arg1}
    </select>

或者 用param1 param2 这个时就是从1开始

    <!--登录-->
    <select id="loginUser" resultType="User">
        select * from t_user where username = #{param1} and password = #{param2}
    </select>

1.3、解决方式2

需要在UserMapper接口的方法中加入@Param注解,这种方式也是最推荐使用的


public interface UserMapper {
    /**
     * @description:用户登录
     * @author: Shenshuanglong
     * @date: 2022/10/17 0017 19:05
     * @param: [username, password]
     * @return: void
     **/
    User loginUser(@Param("username") String username,@Param("password") String password);
}

注意事项:@Param注解里面的名称可以随便起,但要注意映射文件中的sql语句的参数,就得写@Param注解里面的名称

3、#与$的区别

1、$字符串拼接,# 参数站位相当于?
2、$不能够防止sql注入,#可以防止sql注入的
3、$要考虑参类型 ,#不用考虑参数类型  
4、$可以替换sql语句任何一个内容,#只能替换参数

3.1、#的用法

#不用考虑参数类型

#可以防止sql注入

<!--登录-->
<select id="login" resultType="User">
    <!--使用#-->
    select * from t_user where username = #{name} and password = #{password}
</select>
/**
 * 登录
 */
public User login(@Param("name") String name ,@Param("password") String password);
    @Test
    public void testLogin(){
        User u = userMapper.login("jack", "123");//#不用考虑参数类型
        User u = userMapper.login("' or '1' = '1", "' or '1' = '1");//#防止sql注入
        System.out.println(u);
    }

3.2、$的用法

用于字符串拼接

  /**
     * 根据表名来查询
     * @param name
     */
    public List<User> queryByTableName(String name);
 <!--根据表名来查询-->
    <select id="queryByTableName" resultType="User">
        select * from ${name}
    </select>
    @Test
    public void testqueryByTableName(){
        List<User> list = this.userMapper.queryByTableName("t_user");
        for(User user : list){
            System.out.println(user);
        }
    }

$ 需要考虑参数类型

$不防止sql注入

    <!--登录-->
    <select id="login" resultType="User">
         <!--使用$ 需要考虑参数类型-->
        select * from t_user where username = '${name}' and password = '${password}'
    </select>
/**
 * 登录
 */
public User login(@Param("name") String name ,@Param("password") String password);
    @Test
    public void testLogin(){
        User u = userMapper.login("jack", "123");//$考虑参数类型
        User u = userMapper.login("' or '1' = '1", "' or '1' = '1");//$不能防止sql注入
        System.out.println(u);
    }

3.3、#和$的区别

$可以代替所有#
如果传入的数据,不是sql中的字段的时候,就不能够使用#. 

通常使用#。
选择获取参数的时候,首要选择的# 的方式
(1、可以防止sql注入,2、可以不用考虑数据类型,简化书写,3、sql是参数的话的sql,预编译的sql,速度会块一些)
当#用不了的时候,去选择$例如 ,sql需要改变是表名的情况,就可使用$的方式。

总结:能用# 就不选择$

4、resultMap用法

ResultMap 是Mybatis中最为重要的元素,使用ResultMap 可以解决两大问题:

1、pojo属性名与数据库字段名不一致问题

2、完成高级查询,一对一,一对多, 多对多(后面讲)

  <!--查询所有映射-->
    <!--
        id:resultMap的标识
        type:映射的类型
        autoMapping:true(默认) 支持 属性名与字段名自动映射  false:只针对resultMap定义的属性映射
    -->
    <resultMap id="userResultMap" type="User" autoMapping="true">
        
        <!--id一般作为主键,它有自己的标签,实现属性名与字段名一一映射-->
        <id column="id" property="id"/>
        
        <!--除了id以外的字段,用result标签实现属性名与字段名一一映射-->
        <result column="user_name" property="userName"/>
        
        <!--如果,属性名与字段一致,可以省略不写-->
        <result column="sex" property="sex"/>

    </resultMap>
    <!--查询所有-->
    <select id="queryAllUser" resultMap="userResultMap">
        select * from t_user
    </select>

5、sql片段

5.1、用法1

  <!--抽取sql-->
    <sql id="userColumn">
        id ,user_name ,password , sex
    </sql>

    <!--查询所有-->
    <select id="queryAllUser" resultMap="userResultMap">
        select <include refid="userColumn"/> from t_user
    </select>

5.2、用法2

以下用法1抽取的sql只能在本xml中使用,那如何这个抽取的sql其他xml也想使用怎么办呢?用法2就是可以将sql单独抽取到一个文件中,这样其他的xml就可以直接使用

单独创建一个UserSQLMapper.xml文件

<mapper namespace="abc">

    <!--抽取sql-->
    <sql id="userColumn">
        id ,user_name ,passworld , sex
    </sql>

</mapper>
   <!--查询所有-->
    <select id="queryAllUser" resultMap="userResultMap">
        select <include refid="abc.userColumn"/> from t_user
    </select>
<!--注意别忘了:mybatis-config.xml 引入-->
<mapper resource="UserSQLMapper.xml"/>

6、动态sql

业务场景:在我们生活中很多业务常常遇到动态搜索的情况,比如搜索民宿,根据位置,价格,面积等等情况动态搜索,参数等信息可以根据我们需求改变,这里就涉及到了我们Mybatis常说的动态SQL。

Mybatis 动态SQL,通过 if, choose, when, otherwise, trim, where, set, foreach等标签,可组合成非常灵活的SQL语句,从而在提高 SQL 语句的准确性的同时,也大大提高了开发人员的效率。

Mybatis提供了动态SQL,也就是可以根据用户提供的参数,动态决定查询语句依赖的查询条件或SQL语句的内容。

标签名解释
if单条件分支判断
choose、when、otherwise多条件分支判断
where处理sql条件接拼where
set处理sql条件接拼set
foreach循环操作
trim处理sql条件拼接

6.1、动态sql-if标签

UserMapper接口

     /**
     * 查询男性用户,如果输入了姓名,姓名进行模糊查找,如果不输入就按男性用户来查询
     */
     List<User> queryLikeByName(String name);

UserMapper.xml文件 

    <select id="queryLikeByName" resultType="User">
        select * from t_user where sex='男'
        <if test="name != null and name!= ''">
            and username like concat('%',#{name},'%')
        </if>
    </select>

测试类

    @Test
    public void queryNameLike(){
        List<User> users= userMapper.queryNameLike("ja");
        for (User user : users) {
            System.out.println(user);
        }
    }

6.2、动态sql-choose,when,otherwise标签

UserMapper接口

     /**
     * 查询男性用户,如果输入了姓名则按照姓名模糊查找,否则如果输入了年龄则按照年龄查找
     */
     List<User> queryByLikeNameAndAge(@Param("name") String name , @Param("age") Integer age);

UserMapper.xml 

    <select id="queryByLikeNameAndAge" resultType="User">
        select * from t_user where sex='男'
        <choose>
            <when test="name != null and name !=''">
                and user_name like concat('%',#{name},'%')
            </when>
            <when test="age != null and age !=''">
                and age like concat('%',#{age},'%')
            </when>
            <otherwise>
                and id = 14
            </otherwise>
        </choose>
    </select>

测试类 

    @Test
    public void queryByLikeNameAndAge(){
        List<User> users= userMapper.queryByLikeNameAndAge("",23);
        for (User user : users) {
            System.out.println(user);
        }
    }

6.3、动态sql-where标签

UserMapper接口

     /**
     * 查询所有用户,如果输入了姓名按照姓名进行模糊查询,如果输入年龄,按照年龄进行查询
     */
     List<User> queryByAllUserLikeNameAndAge(@Param("name") String name , @Param("age") Integer age);

UserMapper.xml文件

    <select id="queryByAllUserLikeNameAndAge" resultType="User">
        select * from t_user
        <where>
            <if test="name != null and name!=''">
                username like concat('%',#{name},'%')
            </if>
            <if test="age != null and age!=''">
                and age like concat('%',#{age},'%')
            </if>
        </where>
    </select>

测试类

    @Test
    public void queryByAllUserLikeNameAndAge(){
        List<User> users= userMapper.queryByAllUserLikeNameAndAge("",23);
        for (User user : users) {
            System.out.println(user);
        }
    }

运行后发现,满足条件后sql语句会拼接一个where

6.4、动态sql-set标签

UserMapper接口

     /**
     * 如果名字信息不是null,则修改名字, 如果age信息不是null,同时也修改age
     */
     Integer updateByNameOrAge(@Param("name") String name , @Param("age") Integer age , @Param("id") Integer id);

UserMapper.xml文件

    <update id="updateByNameOrAge">
        update t_user
        <set>
            <if test="name != null and name!=''">
                username = #{name},
            </if>
            <if test="age != null and age!=''">
                age = #{age}
            </if>
        </set>
        where id=#{id}
    </update>

测试类

    @Test
    public void updateByNameOrAgeTrim(){
       userMapper.updateByNameOrAgeTrim("rose","女",14);
    }

运行测试可以发现,如果满足条件会自动为sql语句拼接一个set

6.5、动态sql-foreach标签

UserMapper接口

//按照多个id查询用户信息  
List<User> queryIds(@Param("ids") int[] ids);

 UserMapper.xml文件

    <!--按照多个id查询用户信息-->
    <!--
        collection:接受的是一个集合
        item: 表示遍历的变量
        open: 字符串拼接的开头
        close: 字符串拼接的结尾
        separator: 每一个变量拼接的分隔符
    -->
    <select id="queryIds" resultType="User">
        select * from t_user where id in
        <foreach collection="ids" item="id" open="(" close=")" separator=",">
            #{id}
        </foreach>
    </select>

 测试类

    @Test
    public void queryIds(){
        //定义数据库存在的id号放到数组中
        int[] arr={9,10,14};
        List<User> users= userMapper.queryIds(arr);
        for (User user : users) {
            System.out.println(user);
        }
    }

 根据日志可以看到,Mybatis会自动拼接sql语句

6.6、动态sql-trim标签

trim元素的主要功能是可以在自己包含的内容前加上某些前缀,也可以在其后加上某写后缀,与之对应的属性是prefix和suffix;

可以把包含内容的首部某些内容覆盖,即忽略,也可以把尾部的某些内容覆盖,对应的属性是prefixOverrides和suffixOver

UserMapper接口

     /**
     * 查询所有,如果输入姓名,按姓名查询,如果输入性别,就按性别来查询
     */
     List<User> queryNameAndSexTrim(@Param("name") String name , @Param("sex") String sex);

UserMapper.xml文件 

    <!--查询所有,如果输入姓名,按姓名查询,如果输入性别,就按性别来查询-->
    <select id="queryNameAndSexTrim" resultType="User">
        select * from t_user

        <trim prefix="where" prefixOverrides="and">
            <if test="name !=null and name!=''">
                and username=#{name}
            </if>
            <if test="sex !=null and sex!=''">
                and sex=#{sex}
            </if>
        </trim>
    </select>

测试类 

    @Test
    public void queryNameAndSexTrim(){
        List<User> users= userMapper.queryNameAndSexTrim("rose","女");
        for (User user : users) {
            System.out.println(user);
        }
    }

可以看到,Mybatis自动把username前面的and给忽略了 

修改——UserMapper接口

     /**
     * 如果名字信息不是null,则修改名字, 如果sex不为null,同时也修改null
     */
     Integer updateByNameOrAgeTrim(@Param("name") String name , @Param("sex") String sex , @Param("id") Integer id);

UserMapper.xml文件 

    <!--如果名字信息不是null,则修改名字, 如果sex不为null,同时也修改null-->
    <update id="updateByNameOrAgeTrim">
        update t_user
        <trim prefix="set" suffixOverrides="," suffix="where id=#{id}">
            <if test="name!=null and name!=''">
                username=#{name},
            </if>
            <if test="sex!=null and sex!=''">
                sex=#{sex},
            </if>
        </trim>
    </update>

测试类 

    @Test
    public void updateByNameOrAgeTrim(){
       userMapper.updateByNameOrAgeTrim("rose","女",14);
    }

自动把sex=#{sex}后面的,给省略了,因为加了suffixOverrides属性 

7、缓存(面试重点)

在Mybatis里面,有一个非常重要的知识点,就是缓存,缓存分为一级缓存和二级缓存,在面试的时候经常会被问到,下面就讲述一下一级缓存和二级缓存。

7.1、一级缓存

Mybatis的一级缓存的作用域session,当openSession()后,如果执行相同的sql和参数,Mybatis不再执行SQL,而是从缓存中命中并返回;

原理:mybatis执行查询时首先去缓存中命中,如果命中就直接返回,没有命中则执行SQL,从数据库中查。

在mybatis中,一级缓存默认是开启的,并且一直无法关闭(我们没法去管理一级缓存)。

我们在UserMapper.xml文件中加入一个查询sql,根据id进行查询 

    <select id="queryById" resultType="User">
        select * from t_user where id=#{id}
    </select>

UserMapper接口 

//根据id查询用户
User queryById(Integer id);

测试

我们调用两次根据id查询方法

    //测试一级缓存  一级缓存默认开启
    @Test
    public void queryById(){
        User user1 = userMapper.queryById(10);
        System.out.println(user1);
     
    
        User user2 = userMapper.queryById(10);
        System.out.println(user2);
    }

可以看到,日志信息里就查询了一次queryById这个方法,然而第二次查询没有去执行sql,而是去缓存中进行读取了结果,所以不再去调用sql语句,这就是Mybatis框架的效率高、节约资源的优点

我们同样可以进行手动消除缓存,可以通过调用sqlsession中的clearCache方法,或者调用增删改方法,都可以清除缓存。

  @Test
    public void queryById(){
        User user1 = userMapper.queryById(10);
        System.out.println(user1);
        //清空缓存
        sqlSession.clearCache();
        //执行增删改,也可以清空缓存
        // userMapper.deleteById(9);
        User user2 = userMapper.queryById(10);
        System.out.println(user2);
    }

可以看出测试结果,查询了两次,缓存是被消除了

7.2、二级缓存

mybatis 的二级缓存的作用域是一个mapper的namespace ,同一个namespace中查询sql可以从缓存中命中,二级缓存是跨session。

7.3、开启二级缓存的三种方式

第一种

直接在UserMapper.xml映射文件中加入cache标签,表明手动开启二级缓存

<?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="cn.yanqi.mapper.UserMapper">
    
    <!--开启二级缓存-->
    <cache/>

第二种

在mybatis-config.xml全局配置文件中加入setting标签,对cacheEnabled属性进行手动开启,false关闭,true为开启

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

该方式作用是全局的,一旦关闭,整个项目的xml映射文件都会关闭二级缓存,类似于电路图中的总开关。

第三种

直接在sql映射文件中,在select查询标签中加入userCache属性,true为开启,false为关闭,但此方式仅限于该条sql语句

    <select id="queryById" resultType="User" useCache="true">
        select * from t_user where id=#{id}
    </select>

7.4、测试二级缓存

在测试二级缓存之前,需要把一级缓存的sqlsession对象给清除掉,创建一个新的sqlsession对象,注意:新的sqlsession对象前面一定要写上Sqlsession声明,否则只是赋值给了全局sqlsession

    private SqlSession sqlSession;
    private UserMapper userMapper;
    private SqlSessionFactory sqlSessionFactory;
    @Before
    public void setUp() throws Exception {
        //获取核心配置文件
        String resource = "mybatis-config.xml";
        //加载配置文件
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //构建SqlSessionFactory对象
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //构建Sqlsession对象
        sqlSession = sqlSessionFactory.openSession(true);   
        //使用动态代理
        userMapper = sqlSession.getMapper(UserMapper.class);
    }

   //测试一级缓存  一级缓存默认开启
    @Test
    public void queryById(){
        User user1 = userMapper.queryById(10);
        System.out.println(user1);
        //清空缓存
        sqlSession.clearCache();
        //清除sqlsession对象
        sqlSession.close();
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user2 = mapper.queryById(10);
        System.out.println(user2);
    }
}

运行测试类,可以发现出现了异常报错,原因是开启二级缓存后,实体类必须实现Serializable接口,并为实体类声明一个唯一的uuid

修改实体类

@Data
public class User implements Serializable {
    private int id;
    private String username;
    private String password;
    private int age;
    private String sex;
    private String email;
    //为实体类加入一个唯一的uuid
    private static final long serialVersionUID = 42L;
}

再次运行测试类,可以发现,sql语句同样就执行了一次,二级缓存起效了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值