Mybatis的深入了解

1. Mybatis的增删改查

接着前面的内容,演示对用户的增删改查操作

1.1 增加用户

<insert id="saveUser" parameterType="com.linfuxian.entity.User">
    <!-- 配置保存时获取插入的 id -->
    <selectKey keyColumn="id" keyProperty="id" resultType="int">
    	select last_insert_id();
    </selectKey>
    insert into user(username,birthday,sex,address)
    values(#{username},#{birthday},#{sex},#{address})
</insert>
<!--
细节:
parameterType 属性:
代表参数的类型,因为我们要传入的是一个类的对象,所以类型就写类的全名称。
sql 语句中使用#{}字符:
它代表占位符,用于执行语句时替换实际的数据。
具体的数据是由#{}里面的内容决定的。
#{}中内容的写法:
由于我们保存方法的参数是 一个 User 对象,此处要写 User 对象中的属性名称。
它用的是 ognl 表达式。
ognl 表达式:
它是 apache 提供的一种表达式语言,全称是:
Object Graphic Navigation Language 对象图导航语言
它是按照一定的语法格式来获取数据的。
语法格式就是使用 #{对象.对象}的方式
-->

1.2 修改用户

<!-- 更新用户 -->
<update id="updateUser" parameterType="com.linfuxian.entity.User">
    update user set username=#{username},birthday=#{birthday},sex=#{sex},
    address=#{address} where id=#{id}
</update>

1.3 删除用户

<!-- 删除用户 -->
<delete id="deleteUser" parameterType="java.lang.Integer">
delete from user where id = #{uid}
</delete>

1.4 查询用户

<!-- 根据名称模糊查询 方式一 -->
<select id="findByName" resultType="com.itheima.domain.User" parameterType="String">
 select * from user where username like #{username}
</select>
<!-- 根据名称模糊查询 方式二 -->
<select id="findByName" parameterType="string" resultType="com.itheima.domain.User">
 select * from user where username like '%${value}%'
</select>
<!-- 我们在上面将原来的#{}占位符,改成了${value}。注意如果用模糊查询的这种写法,那么${value}的写
法就是固定的,不能写成其它名字。
#{}和${}的区别:
{}表示一个占位符号 通过#{}可以实现 preparedStatement 向占位符中设置值,自动进行 java 类型和 jdbc 类型转换, #{}可以有效防止 sql 注入。 #{}可以接收简单类型值或pojo属性值。如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。 ${}表示拼接sql串, 通过${}可以将 parameterType 传入的内容拼接在 sql 中且不进行 jdbc 类型转换, #{}可以接收简 单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值,${}括号中只能是 value。
-->

2. Mybatis的输出结果封装

​ resultMap 标签可以建立查询的列名和实体类的属性名称不一致时建立对应关系。从而实现封装。 在 select 标签中使用 resultMap 属性指定引用即可。同时 resultMap 可以实现将查询结果映射为复杂类 型的 pojo,比如在查询结果映射对象中包括 pojo 和 list 实现一对一查询和一对多查询。

<!--
type 属性:指定实体类的全限定类名
id 属性:给定一个唯一标识,是给查询 select 标签引用用的。
-->
<resultMap type="com.linfuxian.entity.User" id="userMap">
    <id column="id" property="userId"/>
    <result column="username" property="userName"/>
    <result column="sex" property="userSex"/>
    <result column="address" property="userAddress"/>
    <result column="birthday" property="userBirthday"/>
</resultMap>
<!--
id 标签:用于指定主键字段
result 标签:用于指定非主键字段
column 属性:用于指定数据库列名
property 属性:用于指定实体类属性名称
-->

3. 配置文件SqlMapConfig解析

<?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
        可以在标签内部配置连接数据库的信息。也可以通过属性引用外部配置文件信息
        resource属性: 常用的
            用于指定配置文件的位置,是按照类路径的写法来写,并且必须存在于类路径下。
        url属性:
            是要求按照Url的写法来写地址
            URL:Uniform Resource Locator 统一资源定位符。它是可以唯一标识一个资源的位置。
    -->
    <properties url="file:///D:/code/mybatis/src/main/resources/jdbcConfig.properties">
       <!-- <property name="driver" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="username" value="root"></property>
        <property name="password" value="1234"></property>-->
    </properties>

    <!--使用typeAliases配置别名,它只能配置domain中类的别名 -->
    <typeAliases>
        <!--typeAlias用于配置别名。type属性指定的是实体类全限定类名。alias属性指定别名,当指定了别名就再区分大小写 
        <typeAlias type="com.linfuxian.entity.User" alias="user"></typeAlias>-->

        <!-- 用于指定要配置别名的包,当指定之后,该包下的实体类都会注册别名,并且类名就是别名,不再区分大小写-->
        <package name="com.linfuxian.entity"></package>
    </typeAliases>

    <!--配置环境-->
    <environments default="mysql">
        <!-- 配置mysql的环境-->
        <environment id="mysql">
            <!-- 配置事务 -->
            <transactionManager type="JDBC"></transactionManager>

            <!--配置连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"></property>
                <property name="url" value="${jdbc.url}"></property>
                <property name="username" value="${jdbc.username}"></property>
                <property name="password" value="${jdbc.password}"></property>
            </dataSource>
        </environment>
    </environments>
    <!-- 配置映射文件的位置 -->
    <mappers>
        <!--<mapper resource="com/linfuxian/dao/IUserDao.xml"></mapper>-->
        <!-- package标签是用于指定dao接口所在的包,当指定了之后就不需要在写mapper以及resource或者class了 -->
        <package name="com.linfuxian.dao"></package>
    </mappers>
</configuration>

4. Mybatis的事务控制

Mybatis 框架是对 JDBC 的封装,所以 Mybatis 框架的事务控制方式,本身就是用 JDBC 的 setAutoCommit()方法来设置事务提交方式的。因此Mybatis在增改删操作过程中,我们都要手动进行事务的提交。

我们想要事务自动提交的话,需要创建SqlSession对象时将参数置为true。

public static void main(String[] args)throws Exception {
        //1.读取配置文件
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);
        //3.使用工厂生产SqlSession对象,参数传递true,即可自动提交事务
        SqlSession session = factory.openSession(true);
        //4.使用SqlSession创建Dao接口的代理对象
        IUserDao userDao = session.getMapper(IUserDao.class);
        //5.使用代理对象执行方法
        List<User> users = userDao.findAll();
        for(User user : users){
            System.out.println(user);
        }
        //6.释放资源
        session.close();
        in.close();
    }

5. Mybatis的动态SQL

5.1 if标签

我们根据实体类的不同取值,使用不同的 SQL 语句来进行查询。比如在 id 如果不为空时可以根据 id 查询, 如果 username 不同空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到。

<select id="findByUser" resultType="user" parameterType="user">
select * from user where 1=1
<if test="username!=null and username != '' ">
and username like #{username}
</if>
<if test="address != null">
and address like #{address}
</if>
</select>
<!--
注意:<if>标签的 test 属性中写的是对象的属性名,如果是包装类的对象要使用 OGNL 表达式的写法。
另外要注意 where 1=1 的作用~!
-->

5.2 where 标签

为了简化上面 where 1=1 的条件拼装,我们可以采用where标签来简化开发。

<!-- 根据用户信息查询 -->
<select id="findByUser" resultType="user" parameterType="user">
    select * from user
    <where>
        <if test="username!=null and username != '' ">
            and username like #{username}
        </if>
        <if test="address != null">
            and address like #{address}
        </if>
    </where>
</select>

5.3 foreach标签

<!-- 查询所有用户在 id 的集合之中 -->
<select id="findInIds" resultType="user" parameterType="queryvo">
    select * from user
    <where>
        <if test="ids != null and ids.size() > 0">
            <foreach collection="ids" open="id in ( " close=")" item="uid"
            separator=",">
            #{uid}
            </foreach>
        </if>
    </where>
</select>
<!--
SQL 语句:
select 字段 from user where id in (?)
<foreach>标签用于遍历集合,它的属性:
collection:代表要遍历的集合元素,注意编写时不要写#{}
open:代表语句的开始部分
close:代表结束部分
item:代表遍历集合的每个元素,生成的变量名
sperator:代表分隔符
-->

6. Mybatis复杂查询

6.1 一对多查询

<resultMap type="user" id="userMap">
    <id column="id" property="id"></id>
    <result column="username" property="username"/>
    <result column="address" property="address"/>
    <result column="sex" property="sex"/>
    <result column="birthday" property="birthday"/>
    <!-- collection 是用于建立一对多中集合属性的对应关系,ofType 用于指定集合元素的数据类型
    -->
    <collection property="accounts" ofType="account">
        <id column="aid" property="id"/>
        <result column="uid" property="uid"/>
        <result column="money" property="money"/>
    </collection>
</resultMap>
<!-- 配置查询所有操作 -->
<select id="findAll" resultMap="userMap">
    select u.*,a.id as aid ,a.uid,a.money from user u left outer join account
    a on u.id =a.uid
</select>
<!--
collection
部分定义了用户关联的账户信息。表示关联查询结果集
property="accounts":
关联查询的结果集存储在 User 对象的上哪个属性。
ofType="account":
指定关联查询的结果集中的对象类型即List中的对象类型。此处可以使用别名,也可以使用全限定名。
-->

6.2 多对多查询

多对多查询本质上可以拆分为两个一对多查询,和一对多查询方式一致。

7. Mybatis的延迟加载策略

通过前面的学习,我们已经掌握了 Mybatis 中一对一,一对多,多对多关系的配置及实现,可以实现对象的关联查询。实际开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的账户信息。此时就是我们所说的延迟加载。

延迟加载的好处和坏处:

  • 好处:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速 度要快。
  • 坏处:因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗 时间,所以可能造成用户等待时间变长,造成用户体验下降。

在配置文件中开启延迟加载策略

我们需要在 Mybatis 的配置文件 SqlMapConfig.xml 文件中添加延迟加载的配置。
<!-- 开启延迟加载的支持 -->
<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

7.1 使用 assocation 实现延迟加载

<!-- 建立对应关系 -->
<resultMap type="account" id="accountMap">
    <id column="aid" property="id"/>
    <result column="uid" property="uid"/>
    <result column="money" property="money"/>
    <!-- 它是用于指定从表方的引用实体属性的 -->
    <association property="user" javaType="user"
        select="com.linfuxian.dao.IUserDao.findById"
        column="uid">
    </association>
</resultMap>
<select id="findAll" resultMap="accountMap">
	select * from account
</select>
<!--
select: 填写我们要调用的 select 映射的 id
column : 填写我们要传递给 select 映射的参数
-->

7.2 使用 Collection 实现延迟加载

<resultMap type="user" id="userMap">
    <id column="id" property="id"></id>
    <result column="username" property="username"/>
    <result column="address" property="address"/>
    <result column="sex" property="sex"/>
    <result column="birthday" property="birthday"/>
    <!-- collection 是用于建立一对多中集合属性的对应关系
    ofType 用于指定集合元素的数据类型
    select 是用于指定查询账户的唯一标识(账户的 dao 全限定类名加上方法名称)
    column 是用于指定使用哪个字段的值作为条件查询
    -->
    <collection property="accounts" ofType="account"
        select="com.itheima.dao.IAccountDao.findByUid"
        column="id">
    </collection>
</resultMap>
<!-- 配置查询所有操作 -->
<select id="findAll" resultMap="userMap">
select * from user
</select>
<collection>标签:

8. Mybatis的缓存

像大多数的持久化框架一样,Mybatis 也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。 Mybatis中缓存分为一级缓存,二级缓存。

8.1 一级缓存

一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。

一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。

示例代码:

User user = userDao.findById(41);
System.out.println("第一次查询的用户:"+user);
User user2 = userDao.findById(41);
System.out.println("第二次查询用户:"+user2);
System.out.println(user == user2);

第一次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查 询用户信息。 得到用户信息,将用户信息存储到一级缓存中。 如果 sqlSession 去执行 commit 操作(执行插入、更新、删除),清空 SqlSession 中的一级缓存,这样 做的目的为了让缓存中存储的是最新的信息,避免脏读。 第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存 中获取用户信息。

8.2 二级缓存

二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个 SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。

8.2.1 二级缓存的开启和关闭

第一步:在 SqlMapConfig.xml 文件开启二级缓存

<settings>
<!-- 开启二级缓存的支持 因为 cacheEnabled 的取值默认就为 true,所以这一步可以省略不配置。为 true 代表开启二级缓存;为
false 代表不开启二级缓存。	-->
<setting name="cacheEnabled" value="true"/>
</settings>

第二步:配置相关的 Mapper 映射文件

<!--<cache>标签表示当前这个 mapper 映射将使用二级缓存,区分的标准就看 mapper 的 namespace 值。-->
<?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="com.linfuxian.dao.IUserDao">
<!-- 开启二级缓存的支持 -->
<cache></cache>
</mapper>

第三步:配置 statement 上面的 useCache 属性

<!-- 根据 id 查询 -->
<select id="findById" resultType="user" parameterType="int" useCache="true">
select * from user where id = #{uid}
</select>
<!--
将 UserDao.xml 映射文件中的<select>标签中设置 useCache=”true”代表当前这个 statement 要使用
二级缓存,如果不使用二级缓存可以设置为 false。
注意:针对每次查询都需要最新的数据 sql,要设置成 useCache=false,禁用二级缓存。
-->

示例代码:

 //1.读取配置文件,生成字节输入流
 InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
 //2.获取 SqlSessionFactory
 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); 
 SqlSession sqlSession1 = factory.openSession();
 IUserDao dao1 = sqlSession1.getMapper(IUserDao.class);
 User user1 = dao1.findById(41);
 System.out.println(user1);
 sqlSession1.close();//一级缓存消失
 SqlSession sqlSession2 = factory.openSession();
 IUserDao dao2 = sqlSession2.getMapper(IUserDao.class);
 User user2 = dao2.findById(41);
 System.out.println(user2);
 sqlSession2.close();
 System.out.println(user1 == user2);

经过上面的代码测试,我们发现执行了两次查询,并且在执行第一次查询后,我们关闭了一级缓存,再去执行第二 次查询时,我们发现并没有对数据库发出 sql 语句,所以此时的数据就只能是来自于我们所说的二级缓存。

当我们在使用二级缓存时,所缓存的类一定要实现 java.io.Serializable 接口,这种就可以使用序列化方式来保存对象。

posted @ 2021-02-23 10:54  渺渺孤烟起  阅读(88)  评论(0)    收藏  举报