MyBatis入门四

1 MyBatis 延迟加载迟略

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

1.1 使用 association 实现一对一延迟加载

​ 查询账户(Account)信息并且关联查询用户(User)信息。当我们需要查询用户(User)信息时再查询用户(User)信息。把对用户(User)信息的按需去查询就是延迟加载。

1.1.1 账户的持久层DAO接口

//在Account里添加User属性
public interface IAccountDao {
    List<Account> findAllAccountUser();
}

1.1.2 账户的持久层映射配置

<resultMap id="accountMap" type="cn.pine.domain.Account">
    <id column="id" property="id"/>
    <result column="uid" property="uid"/>
    <result column="money" property="money"/>
    <association property="user" javaType="cn.pine.domain.User"
                 select="cn.pine.dao.IUserDao.findById"
                 column="uid">
    </association>
</resultMap>
<select id="findAllAccountUser" resultMap="accountMap">
    SELECT * FROM account
</select>

1.1.3 在 SqlMapConfig.xml 中开启延迟加载策略

<settings>
    <!--注意标签顺序,在typeAliases前-->
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading"value="false"></setting>
</settings>

1.1.4 测试

@Test
public void testFindAllAccountUser(){
    List<Account> allAccountUser = accountDao.findAllAccountUser();
}

由于只查询 Account 对象并没有取User对象,所以返回的结果中没有发出关联查询的语句

DEBUG IAccountDao.findAllAccountUser  - ==>  Preparing: SELECT * FROM account 
IAccountDao.findAllAccountUser  - ==> Parameters: 
IAccountDao.findAllAccountUser  - <==      Total: 3

在测试中取 User 对象时,返回结果便发出关联查询。

DEBUG IAccountDao.findAllAccountUser  - ==>  Preparing: SELECT * FROM account 
DEBUG IAccountDao.findAllAccountUser  - ==> Parameters: 
DEBUG IAccountDao.findAllAccountUser  - <==      Total: 3
DEBUG  cn.pine.dao.IUserDao.findById  - ==>  Preparing: SELECT * FROM USER WHERE id = ? 
DEBUG  cn.pine.dao.IUserDao.findById  - ==> Parameters: 46(Integer)
DEBUG  cn.pine.dao.IUserDao.findById  - <==      Total: 1
User{id=46, username='赵六', birthday=Wed Mar 07 17:37:26 CST 2018, sex='男', address='杭州'}
DEBUG  cn.pine.dao.IUserDao.findById  - ==>  Preparing: SELECT * FROM USER WHERE id = ? 
DEBUG  cn.pine.dao.IUserDao.findById  - ==> Parameters: 45(Integer)
DEBUG  cn.pine.dao.IUserDao.findById  - <==      Total: 1
User{id=45, username='李四', birthday=Sun Mar 04 12:04:06 CST 2018, sex='男', address='深圳'}
User{id=46, username='赵六', birthday=Wed Mar 07 17:37:26 CST 2018, sex='男', address='杭州'}

1.2 使用 Collection 实现一对多延迟加载

查询用户(User)信息并且关联查询账户(Account)信息。

1.2.1 在 User 中加入List<Account>属性

public class User implements Serializable {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
    private List<Account> accounts;

1.2.2 用户和账户持久层接口的方法

List<User> lazyFindAllUserAccount();
List<Account> findByUid(Integer uid);

1.2.3 用户持久层映射配置

<resultMap id="lazyUserMap" type="user">
    <id column="id" property="id"/>
    <result column="username" property="username"/>
    <result column="address" property="address"/>
    <result column="sex" property="sex"/>
    <result column="birthday" property="birthday"/>
    <collection property="acccounts" ofType="cn.pine.domain.Account"
                select="cn.pine.dao.IAccountDao.findById"
                column="id">
    </collection>
</resultMap>
<select id="lazyFindAllUserAccount" resultMap="lazyUserMap">
    SELECT * FROM user
</select>

1.2.4 账户持久层映射配置

<select id="findById" resultType="cn.pine.domain.Account" parameterType="int">
    SELECT * FROM account WHERE uid = #{uid}
</select>

测试

不取 User 中的 Account 集合时,返回结果中不发送关联查询

@Test
public void testLazyFindAllUserAccount(){
    List<User> users = userDao.lazyFindAllUserAccount();
}
DEBUG UserDao.lazyFindAllUserAccount-=Preparing: SELECT* FROM user
DEBUG UserDao.lazyFindAllUserAccount -=> Parameters:
DEBUG UserDao.lazyFindAllUserAccount -<== Total: 6

2 MyBatis 缓存

​ MyBatis 通过缓存策略减少数据库的查询次数,提高性能,其中分为一级缓存与二级缓存。

2.1 一级缓存

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

设置useCache为true

<?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.pine.dao.IUserDao">
    <select id="findById" resultType="user" parameterType="int" useCache="true">
        SELECT * FROM USER WHERE id = #{id}
    </select>
</mapper>

2.1.2 测试

@Test
public void testFindByID(){
    User user1 = userDao.findById(41);
    System.out.println("第一次查询:" + user1);
    User user2 = userDao.findById(41);
    System.out.println("第一次查询:" + user2);
    System.out.println(user1==user2);
}

这里查询两次,但是返回结果只发出一次查询,并且两次查询结果,地址值相同。由此可见缓存存在。

第一次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 41 的用户信息,如果没有,从数据库查询用户信息。

得到用户信息,将用户信息存储到一级缓存中。如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。

第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 41 的用户信息,缓存中有,直接从缓存中获取用户信息。

-==> Preparing: SELECT*FROM USER WHERE id = ?
-> Parameters: 41(Integer)
-<== Total: 1
第一次查询:cn.pine.domain.User@4fcd19b3
第一次查询:cn.pine.domain.User@4fcd19b3
true

2.2 二级缓存

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

首先开启二级缓存。

SqlSession1去查询用户信息,查询到用户信息会将查询数据存储到二级缓存中。

SqlSession2去查询与SqlSession1相同的用户信息,首先会去缓存中找是否存在数据,如果存在直接从缓存中取出数据

如果SqlSession3去执行相同 mapper 映射下SQL,执行commit提交,将会清空该mapper映射下的二级缓存区域的数据。

2.2.1 SqlMapConfig.xml 开启二级缓存

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

2.2.2 配置相关 Mapper 映射

<?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.pine.dao.IUserDao">
    <!-- 开启二级缓存的支持 -->
    <cache></cache>
    <select id="findById" resultType="user" parameterType="int" useCache="true">
        SELECT * FROM USER WHERE id = #{id}
    </select>
</mapper>

2.2.3 测试

在执行第一次查询后关闭`SqlSession

public class MybatisTest {

    private InputStream in;
    private SqlSessionFactory factory;

    @Before
    public void init() throws Exception{
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        factory = builder.build(in);

    }

    @After
    public void destroy() throws Exception{
        in.close();
    }

    @Test
    public void testFindByID(){
        SqlSession session1 = factory.openSession();
        SqlSession session2 = factory.openSession();
        IUserDao dao1 = session1.getMapper(IUserDao.class);
        IUserDao dao2 = session2.getMapper(IUserDao.class);
        User user1 = dao1.findById(41);
        System.out.println(user1);
        session1.close();
        User user2 = dao2.findById(41);
        System.out.println(user2);
        session2.close();
        System.out.println(user1 == user2);
    }
}

- ==> Preparing:SELECT* FROM USER WHERE id = ?
- ==> Parameters: 41(Integer)
- <== Total: 1

3 MyBatis 注解

3.1 常用注解

@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与 @Result 一起使用,封装多个结果集
@ResultMap:实现引用 @Results 定义的封装
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
@SelectProvider:实现动态 SQL 映射
@CacheNamespace:实现注解二级缓存的使用

3.2 使用注解实现 CRUD 操作

3.2.1 实体类

public class User implements Serializable {
	//与数据库列名不一致,省略getter,setter,toString
    private Integer userId;
    private String userName;
    private Date userBirthday;
    private String userSex;
    private String userAddress;
}

3.2.2 注解方式开发持久层接口

public interface IUserDao {

    @Select("select * from user")
    @Results(id = "userMap",
                value = {
                        @Result(id=true, column = "id",property = "userId"),
                        @Result(column="username",property="userName"),
                        @Result(column="sex",property="userSex"),
                        @Result(column="address",property="userAddress"),
                        @Result(column="birthday",property="userBirthday")
                })
    List<User> findAll();

    @Select("select * from user where id = #{uid}")
    @ResultMap("userMap")
    User findById(Integer userId);

    @Insert("insert into user(username,sex,birthday,address) values (#{userName},#{userSex},#{userBirthday},#{userAddress})")
    @SelectKey(keyColumn = "id",keyProperty = "userId", resultType = Integer.class, before = false, statement = {"select last_insert_id()"})
    int saveUser(User user);

    @Update("update user set username=#{userName},address=#{userAddress},sex=#{userSex},birthday=#{userBirthday} where id =#{userId} ")
    int updateUser(User user);

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

    @Select("select count(*) from user")
    int findTotal();

    @Select("select * from user where username like #{username}")
    List<User> findByName(String name);
}

3.2.3 测试代码

public class MybatisTest {
    private InputStream in;
    private SqlSessionFactory factory;
    private SqlSession session;
    private IUserDao userDao;

    @Before
    public void init()throws Exception{
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        factory = builder.build(in);
        session = factory.openSession();
        userDao = session.getMapper(IUserDao.class);
    }
    @After
    public void destroy()throws Exception {
        session.commit();
        session.close();
        in.close();
    }

    @Test
    public void testFindAll(){
        List<User> users = userDao.findAll();
        for (User user : users) {
            System.out.println(user);
        }
    }

    @Test
    public void testSave(){
        User user = new User();
        user.setUserName("Test_Save");
        user.setUserSex("男");
        user.setUserAddress("哈尔滨");
        user.setUserBirthday(new Date());

        int res = userDao.saveUser(user);
        System.out.println("数据库影响行数---" + res);
        System.out.println("插入的主键值---" + user.getUserId());
    }
    @Test
    public void testUpdate() {
        User user = userDao.findById(48);
        user.setUserBirthday(new Date());
        user.setUserSex("男");
        int res = userDao.updateUser(user);
        System.out.println(res);
    }

    @Test
    public void testDelete() {
        int res = userDao.deleteUser(51);
        System.out.println(res);
    }

    @Test
    public void testFindTotal(){
        int res = userDao.findTotal();
        System.out.println(res);
    }

    @Test
    public void testFindByName() {
        List<User> users = userDao.findByName("%四%");
        for(User user : users) {
            System.out.println(user);
        } 
    }
}

3.3 使用注解实现复杂关系映射开发

@Results 注解
代替的是标签<resultMap>
该注解中可以使用单个@Result 注解,也可以使用@Result 集合
@Results({@Result(),@Result()})或@Results(@Result())

@Result 注解
代替了 <id>标签和<result>标签
@Result 属性
id 是否是主键字段
column 数据库的列名
property 需要装配的属性名
one 需要使用的@One 注解(@Result(one=@One)()))
many 需要使用的@Many 注解(@Result(many=@many)()))

@One 注解(一对一)
代替了<assocation>标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。
@One 注解属性介绍:
select 指定用来多表查询的 sqlmapper
fetchType 会覆盖全局的配置参数 lazyLoadingEnabled。。
使用格式:
@Result(column=" ",property="",one=@One(select=""))

@Many 注解(多对一)
代替了<Collection>标签,是是多表查询的关键,在注解中用来指定子查询返回对象集合。
注意:聚集元素用来处理“一对多”的关系。需要指定映射的 Java 实体类的属性,属性的 javaType
(一般为 ArrayList)但是注解中可以不定义;
使用格式:
@Result(property="",column="",many=@Many(select=""))

3.3.1 使用注解实现一对一复杂关系映射和延迟加载

​ 加载账户信息延迟加载用户信息。

3.3.2.1 Account 实体类

​ Account 类中添加 User 属性

public class Account implements Serializable {
    private Integer id;
    private Integer uid;
    private Double money;
    private User user;
}
3.3.2.2 账户的持久层接口使用注解配置
public interface IAccountDao {

    @Select("select * from account")
    @Results(id="acountMap", value={
            @Result(id = true,column = "id",property = "id"),
            @Result(column="uid",property="uid"),
            @Result(column="money",property="money"),
            @Result(column="uid",
                    property="user",
                    one=@One(select="cn.pine.dao.IUserDao.findById",
                            fetchType= FetchType.LAZY))
    })
    List<Account> findAll();
}
3.3.2.3 测试
@Test
public void testFindAll(){
    List<Account> accounts =  accountDao.findAll();
    /* 开启注释,只发送3次查询语句 
       关闭后,多发两次查询
    for (Account account : accounts) {
        System.out.println(account);
        System.out.println(account.getUser());
    }
    */
}

3.3.2 使用注解实现一对多复杂关系映射和延迟加载

​ 加载用户信息,延迟加载账户信息。

3.3.2.1 User 类加入List<Account>
public class User implements Serializable {

    private Integer userId;
    private String userName;
    private Date userBirthday;
    private String userSex;
    private String userAddress;
    private List<Account> accounts;
}
3.3.2.2 注解配置用户持久层
public interface IUserDao {

    @Select("select * from user")
    @Results(id = "userMap",
                value = {
                        @Result(id=true, column = "id",property = "userId"),
                        @Result(column="username",property="userName"),
                        @Result(column="sex",property="userSex"),
                        @Result(column="address",property="userAddress"),
                        @Result(column="birthday",property="userBirthday"),
                        @Result(column = "id",property = "accounts",
                            many = @Many(
                                    select = "cn.pine.dao.IAccountDao.findByUid",
                                    fetchType = FetchType.LAZY
                            ))
                })
    List<User> findAll();
}
3.3.2.3 注解配置账户持久层
public interface IAccountDao {
    
    @Select("select * from account where uid = #{uid}")
    List<Account> findByUid(Integer userId);
}
3.3.2.4 测试
@Test
public void testFindAll(){
    List<User> users = userDao.findAll();
   /* for (User user : users) {
        System.out.println("----每个用户的信息-----");
        System.out.println(user);
        System.out.println(user.getAccounts());
    }*/
}

3.4 基于注解的二级缓存

3.4.1 SqlMapConfig.xml 开启二级缓存支持

<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>

3.4.2 持久层接口使用注解配置耳机缓存

@CacheNamespace
public interface IUserDao {}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值