menu 若在
Mybatis
36 浏览 | 2021-03-10 | 阅读时间: 约 25 分钟 | 分类: JAVA | 标签: Mybatis
请注意,本文编写于 33 天前,最后修改于 33 天前,其中某些信息可能已经过时。

1. 框架

什么是框架

​ 框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法;另一种定义认为,框架是可被应用开发者定制的应用骨架。前者是从应用方面而后者是从目的方面给出的定义。 简而言之,框架其实就是某种应用的半成品,就是一组组件,供你选用完成你自己的系统。简单说就是使用别人搭好的舞台,你来做表演。而且,框架一般是成熟的,不断升级的软件。

框架要解决的问题

​ 框架要解决的最重要的一个问题是技术整合的问题,在J2EE的 框架中,有着各种各样的技术,不同的软件企业需要从J2EE中选择不同的技术,这就使得软件企业最终的应用依赖于这些技术,技术自身的复杂性和技术的风险性将会直接对应用造成冲击。而应用是软件企业的核心,是竞争力的关键所在,因此应该将应用自身的设计和具体的实现技术解耦。这样,软件企业的研发将集中在应用的设计上,而不是具体的技术实现,技术实现是应用的底层支撑,它不应该直接对应用产生影响。 框架一般处在低层应用平台(如J2EE)和高层业务逻辑之间的中间层。

软件开发的分层重要性

​ 框架的重要性在于它实现了部分功能,并且能够很好的将低层应用平台和高层业务逻辑进行了缓和。为了实现软件工程中的“高内聚、低耦合”。把问题划分开来各个解决,易于控制,易于延展,易于分配资源。我们常见的MVC软件设计思想就是很好的分层思想。

MyBatis框架概述

​ mybatis是一个优秀的基于java的持久层框架,它内部封装了jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。 mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。 采用ORM思想解决了实体和数据库映射的问题,对jdbc进行了封装,屏蔽了jdbc api底层访问细节,使我们不用与jdbc api打交道,就可以完成对数据库的持久化操作。 为了我们能够更好掌握框架运行的内部过程,并且有更好的体验,下面我们将从自定义Mybatis框架开始来学习框架。此时我们将会体验框架从无到有的过程体验,也能够很好的综合前面阶段所学的基础。

2. Mybatis框架快速入门

目录结构:

2.1创建maven工程

2.2编辑Pom.xml文件

在pom.xml文件中添加Mybatis3.4.5的坐标,如下:

    <dependencies>
        <!--        mybatis坐标-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>
        <!--        mysql驱动坐标-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.20</version>
            <scope>runtime</scope>
        </dependency>
        <!--        日志坐标-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
        <!--        单元测试坐标-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

2.3编写User实体类:

package cn.msy.domain;


import java.util.Date;

public class User {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", birthday=" + birthday +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

2.4编写持久层接口UserDaoInterface

package cn.msy.dao;

import cn.msy.domain.User;
import org.apache.ibatis.annotations.Select;

import java.util.List;
//用户持久层接口
public interface UserDaoInterface {
    /**
     * 查询所有操作
     * @return
     */
    List<User> findAll();

    /**
     * 注解方式
     * @return
     */
    @Select("select * from user where sex='男'")
    List<User> findMan();
}

2.5 编写持久层接口的映射文件UserDaoInterface.xml

xml方式要求:

​ 创建位置:必须和持久层接口在相同的包中。

​ 名称:必须以持久层接口名称命名文件名,扩展名是.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.msy.dao.UserDaoInterface">
    <select id="findAll" resultType="cn.msy.domain.User">
        select * from user
    </select>
</mapper>

2.6 编写SqlMapConfig.xml配置文件

SqlMapConfig.xml中配置的内容和顺序

properties(属性) 
    --property 
-settings(全局配置参数) 
    --setting -typeAliases(类型别名) 
    --typeAliase 
    --package 
-typeHandlers(类型处理器) 
-objectFactory(对象工厂) 
-plugins(插件) 
-environments(环境集合属性对象) 
    --environment(环境子属性对象) 
        ---transactionManager(事务管理) 
        ---dataSource(数据源) 
-mappers(映射器) 
    --mapper 
    --package

properties(属性)

在使用properties标签配置时,我们可以采用两种方式指定属性配置。

第一种

<properties> 
    <property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>         <property name="jdbc.url" value="jdbc:mysql:///eesy"/>
    <property name="jdbc.username" value="root"/> 
    <property name="jdbc.password" value="1234"/> 
</properties>

第二种

在classpath下定义db.properties文件

jdbc.driver=com.mysql.jdbc.Driver 
jdbc.url=jdbc:mysql:///eesy 
jdbc.username=root 
jdbc.password=1234

properties标签配置

    <!--    引入外部配置文件 相对路径-->
    <properties resource="jdbcConfig.properties"></properties>

此时我们的dataSource标签就变成了引用上面的配置

<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>

typeAliases(类型别名)

Mybatis支持的默认别名,我们也可以采用自定义别名方式来开发。

自定义别名:

<typeAliases> 
    <!-- 单个别名定义 --> 
    <typeAlias alias="user" type="com..domain.User"/> 
    <!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以) -->     <package name="com..domain"/> 
    <package name="其它包"/> 
</typeAliases>

mappers(映射器)

使用相对于类路径的资源 如:
<mapper resource="cn/msy/dao/IUserDao.xml" />
使用mapper接口类路径 如:
<mapper class="cn.msy.dao.UserDao"/> 
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
注册指定包下的所有mapper接口 如:
<package name="cn.msy.dao.UserDaoInterface"/> 
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
<?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">
<!--mybatis的主配置文件-->
<configuration>
    <!--    配置别名-->
    <typeAliases>
        <package name="cn.msy.domain"></package>
    </typeAliases>
    
    <!--    配置环境-->
    <environments default="mysql">
        <!--        配置MySQL的环境-->
        <environment id="mysql">
            <!--            配置事务类型-->
            <transactionManager type="JDBC"></transactionManager>
            <!--            配置数据源(连接池)-->
            <dataSource type="POOLED">
                <!--            配置连接数据库的4个基本信息-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql:///mybatis?serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="123"/>
            </dataSource>
        </environment>
    </environments>

    <!--    指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件-->
    <mappers>
        <!--        xml配置 告知mybatis映射配置的位置-->
        <!--        <mapper resource="cn/msy/dao/UserDaoInterface.xml"/>-->
        <!--        注解配置 告知mybatis映射配置的位置-->
        <mapper class="cn.msy.dao.UserDaoInterface"/>
    </mappers>
</configuration>

2.7 编写测试类

package cn.msy.test;

import cn.msy.dao.UserDaoInterface;
import cn.msy.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class MybatisTest {

    public static void main(String[] args) throws IOException {
        //第一步:读取配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        //第二步:创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
        SqlSessionFactory factory=builder.build(resourceAsStream);
        //第三步:创建SqlSession
        SqlSession session = factory.openSession();
        //第四步:创建Dao接口的代理对象
        UserDaoInterface userDao = session.getMapper(UserDaoInterface.class);
        //第五步:执行dao中的方法
        //xml
        List<User> all = userDao.findAll();
        for (User user : all) {
            System.out.println(user);
        }
        System.out.println("=========================");
        //注解
        List<User> man = userDao.findMan();
        for (User user : man) {
            System.out.println(user);
        }
        //第六步:释放资源
        session.close();
        resourceAsStream.close();
    }
}

2.8 小结

​ 通过快速入门示例,发现使用mybatis是非常容易的一件事情,因为只需要编写Dao接口并且按照mybatis要求编写两个配置文件,就可以实现功能。远比我们之前的jdbc方便多了。(我们使用注解之后,将变得更为简单,只需要编写一个mybatis配置文件就够了。)

​ 但是,这里面包含了许多细节,比如为什么会有工厂对象(SqlSessionFactory),为什么有了工厂之后还要有构建者对象(SqlSessionFactoryBuilder),为什么IUserDao.xml在创建时有位置和文件名的要求等等。

​ 请注意:我们讲解自定义Mybatis框架,不是让大家回去自己去写个mybatis,而是让我们能更好了了解mybatis内部是怎么执行的,在以后的开发中能更好的使用mybatis框架,同时对它的设计理念(设计模式)有一个认识。

在使用基于注解的Mybatis配置时,请移除xml的映射配置(UserDaoInterface.xml)

mybatis的环境搭建
    第一步:创建maven工程并导入坐标
    第二步:创建实体类和dao的接口
    第三步:创建Mybatis的主配置文件
            SqlMapConifg.xml
    第四步:创建映射配置文件
            UserDaoInterface.xml
环境搭建的注意事项:
    第一个:创建UserDaoInterface.xml 和 UserDaoInterface.java时名称是为了和我们之前的知识保持一致。
        在Mybatis中它把持久层的操作接口名称和映射文件也叫做:Mapper
        所以:UserDaoInterface 和 UserInterfaceMapper是一样的
    第二个:在idea中创建目录的时候,它和包是不一样的
        包在创建时:cn.msy.dao它是三级结构
        目录在创建时:cn.msy.dao是一级目录
    第三个:mybatis的映射配置文件位置必须和dao接口的包结构相同
    第四个:映射配置文件的mapper标签namespace属性的取值必须是dao接口的全限定类名
    第五个:映射配置文件的操作配置(select),id属性的取值必须是dao接口的方法名

    当我们遵从了第三,四,五点之后,我们在开发中就无须再写dao的实现类。
mybatis的入门案例
    第一步:读取配置文件
    第二步:创建SqlSessionFactory工厂
    第三步:创建SqlSession
    第四步:创建Dao接口的代理对象
    第五步:执行dao中的方法
    第六步:释放资源

    注意事项:
        不要忘记在映射配置中告知mybatis要封装到哪个实体类中
        配置的方式:指定实体类的全限定类名
    
    mybatis基于注解的入门案例:
        把UserDaoInterface.xml移除,在dao接口的方法上使用@Select注解,并且指定SQL语句
        同时需要在SqlMapConfig.xml中的mapper配置时,使用class属性指定dao接口的全限定类名。
明确:
    我们在实际开发中,都是越简便越好,所以都是采用不写dao实现类的方式。
    不管使用XML还是注解配置。
    但是Mybatis它是支持写dao实现类的。

3. 基于代理Dao实现CRUD操作

3.1 根据ID查询

3.1.1 在持久层中添加方法

package cn.msy.dao;

import cn.msy.domain.QueryVo;
import cn.msy.domain.User;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

import java.util.List;
//用户持久层接口
public interface UserDaoInterface {
    /**
     * 根据id查询用户
     */
    User findById(Integer id);

}

3.1.2 在用户配置文件中配置

<?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.msy.dao.UserDaoInterface">
    <select id="findById" parameterType="INT" resultType="cn.msy.domain.User">
        select * from user where id=#{uid};
    </select>
</mapper>

3.1.3 在测试类中添加

package cn.msy.test;

import cn.msy.dao.UserDaoInterface;
import cn.msy.domain.QueryVo;
import cn.msy.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class MybatisTest {

    private InputStream resourceAsStream;
    private SqlSession session;
    private UserDaoInterface userDao;

    @Before
    public void init() throws IOException {
        //第一步:读取配置文件
        resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        //第二步:创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(resourceAsStream);
        //第三步:创建SqlSession
        //session = factory.openSession(true);//自动提交
        session = factory.openSession();
        //第四步:创建Dao接口的代理对象
        userDao = session.getMapper(UserDaoInterface.class);
    }

    @After
    public void destroy() throws IOException {
        //提交事务
        session.commit();
        //第六步:释放资源
        session.close();
        resourceAsStream.close();
    }

    @Test
    public void testFindById() throws IOException {
        //第五步:执行dao中的方法
        User byId = userDao.findById(48);
        System.out.println(byId);
    }
}

3.2 保存操作

3.2.1 在持久层中添加方法

/** 
* 保存用户 
* @param user 
* @return 影响数据库记录的行数 
*/ 
int saveUser(User user);

3.2.2 在用户配置文件中配置

<!-- 保存用户-->
    <insert id="saveUser" parameterType="user"> 
        <!-- parameterType本来是全类名但是在配置文件中声明了domian文件夹下的别名所以可以直接用user-->
        <!-- 配置插入操作后,获取插入数据的id -->
        <selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
            select last_insert_id();
        </selectKey>
        insert into user (username,address,sex,birthday)values (#{username},#{address},#{sex},#{birthday});
    </insert>

细节:

parameterType属性:

​ 代表参数的类型,因为我们要传入的是一个类的对象,所以类型就写类的全名称。 sql语句中使用#{}字符:

​ 它代表占位符,相当于原来jdbc部分所学的?,都是用于执行语句时替换实际的数据。

​ 具体的数据是由#{}里面的内容决定的。

{}中内容的写法:

​ 由于我们保存方法的参数是 一个User对象,此处要写User对象中的属性名称。

​ 它用的是ognl表达式。

ognl表达式:

​ 它是apache提供的一种表达式语言,全称是:

​ Object Graphic Navigation Language 对象图导航语言

​ 它是按照一定的语法格式来获取数据的。

​ 语法格式就是使用 #{对象.对象}的方式

​ #{user.username}它会先去找user对象,然后在user对象中找到username属性,并调用getUsername()方法把值取出来。但是我们在parameterType属性上指定了实体类名称,所以可以省略user.而直接写username。

3.2.3 在测试类中添加

    @Test
    public void testSave() throws IOException {
        User user = new User();
        user.setAddress("北京故宫");
        user.setBirthday(new Date());
        user.setUsername("mybatis saveUser");
        user.setSex("男");
        System.out.println("之前" + user.getId());
        //第五步:执行dao中的方法
        userDao.saveUser(user);
        System.out.println("之后" + user.getId());
    }

3.2.4 问题扩展:新增用户id的返回值

新增用户后,同时还要返回当前新增用户的id值,因为id是由数据库的自动增长来实现的,所以就相当于我们要在新增后将自动增长auto_increment的值返回。

<insert id="saveUser" parameterType="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>

3.3 用户更新

3.3.1 在持久层中添加方法

    /**
     * 更新用户
     */
    void updateUser(User user);

3.3.2 在用户配置文件中配置

    <update id="updateUser" parameterType="user">
        update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id};
    </update>

3.3.3 在测试类中添加

    @Test
    public void testUpdate() throws IOException {
        User user = new User();
        user.setId(49);
        user.setAddress("北京故宫");
        user.setBirthday(new Date());
        user.setUsername("mybatis updateUser");
        user.setSex("女");
        //第五步:执行dao中的方法
        userDao.updateUser(user);
    }

3.4用户删除

3.4.1 在持久层中添加方法

    /**
     * 删除用户
     */
    void deleteUser(Integer id);

3.4.2 在用户配置文件中配置

    <delete id="deleteUser" parameterType="INT">
        delete from user where id=#{uid};
    </delete>

3.4.3 在测试类中添加

    @Test
    public void testDelete() throws IOException {
        //第五步:执行dao中的方法
        userDao.deleteUser(49);
    }

3.5用户模糊查询

3.5.1 在持久层中添加方法

    /**
     * 根据姓名模糊查询用户
     */
    List<User> findByName(String name);

3.5.2 在用户配置文件中配置

    <select id="findByName" parameterType="String" resultType="cn.msy.domain.User">
        select * from user where username like #{uname}
    </select>

3.5.3 在测试类中添加

    @Test
    public void testFindByName() throws IOException {
        //第五步:执行dao中的方法
        List<User> byName = userDao.findByName("%王%");

        for (User user : byName) {
            System.out.println(user);
        }
    }

​ 我们在配置文件中没有加入%来作为模糊查询的条件,所以在传入字符串实参时,就需要给定模糊查询的标识%。配置文件中的#{username}也只是一个占位符,所以SQL语句显示为“?”。

3.5.4 模糊查询的另一种配置方式

第一步:修改SQL语句的配置,配置如下:

<!-- 根据名称模糊查询 --> 
<select id="findByName" parameterType="string" resultType="com..domain.User"> 
    select * from user where username like '%${value}%'
</select>

​ 在上面将原来的#{}占位符,改成了${value}。注意如果用模糊查询的这种写法,那么${value}的写法就是固定的,不能写成其它名字。

第二步:测试,如下:

    @Test
    public void testFindByName() throws IOException {
        //第五步:执行dao中的方法
        List<User> byName = userDao.findByName("%王%");

        for (User user : byName) {
            System.out.println(user);
        }
    }

​ 可以发现,我们在程序代码中就不需要加入模糊查询的匹配符%了,这两种方式的实现效果是一样的,但执行的语句是不一样的。

3.5.2 #{}与${}的区别

{}表示一个占位符号

​ 通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以有效防止sql注入。 #{}可以接收简单类型值或pojo属性值。 如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。

${}表示拼接sql串

​ 通过${}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换, ${}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,${}括号中只能是value。

3.6 查询使用聚合函数

3.6.1 在持久层中添加方法

    /**
     * 统计查询
     */
    Integer findTotal();

3.6.2 在用户配置文件中配置

    <select id="findTotal" resultType="int">
        select count(*) from user
    </select>

3.6.3 在测试类中添加

    @Test
    public void findTotal() throws IOException {
        //第五步:执行dao中的方法
        Integer total = userDao.findTotal();
        System.out.println(total);
    }

3.7 Mybatis与JDBC的比较

1.数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。

解决:

​ 在SqlMapConfig.xml中配置数据链接池,使用连接池管理数据库链接。

2.Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。

解决:

​ 将Sql语句配置在XXXXmapper.xml文件中与java代码分离。

3.向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数对应。

解决:

​ Mybatis自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型。

4.对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。

解决:

​ Mybatis自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型。

4. Mybatis参数深入

4.1 parameterType配置参数

4.1.1 使用说明

SQL语句传参,使用标签的parameterType属性来设定。该属性的取值可以是基本类型,引用类型(例如:String类型),还可以是实体类类型(POJO类)。同时也可以使用实体类的包装类。

4.1.2 注意事项

​ 基本类型和String我们可以直接写类型名称,也可以使用包名.类名的方式,例如:java.lang.String。

​ 实体类类型,目前我们只能使用全限定类名。

​ 究其原因,是mybaits在加载时已经把常用的数据类型注册了别名,从而我们在使用时可以不写包名,而我们的是实体类并没有注册别名,所以必须写全限定类名。

​ 在mybatis的官方文档的说明(第19页)

这些都是支持的默认别名。我们也可以从源码角度来看它们分别都是如何定义出来的。 可以参考TypeAliasRegistery.class的源码。

4.2 传递pojo包装对象

​ 开发中通过pojo传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。

​ Pojo类中包含pojo。

​ 需求:根据用户名查询用户信息,查询条件放到QueryVo的user属性中。

4.2.1 编写QueryVo

public class QueryVo implements Serializable { 
    private User user;
    public User getUser() { 
        return user; 
    } 
    public void setUser(User user) { 
        this.user = user; 
    } 
}

4.2.2 编写持久层接口

public interface IUserDao { 
    /** 
    * 根据QueryVo中的条件查询用户 
    * @param vo 
    * @return */ 
    List<User> findByVo(QueryVo vo); 
}

4.2.3 持久层接口的映射文件

<!-- 根据用户名称模糊查询,参数变成一个QueryVo对象了 --> 
<select id="findByVo" resultType="com..domain.User" parameterType="com..domain.QueryVo"> 
    select * from user where username like #{user.username}; 
</select>

4.2.4 测试包装类作为参数

@Test 
public void testFindByQueryVo() 
{ 
    QueryVo vo = new QueryVo(); 
    User user = new User(); user.setUserName("%王%"); 
    vo.setUser(user); List<User> users = userDao.findByVo(vo); 
    for(User u : users) 
    { 
        System.out.println(u); 
    } 
}

5. Mybatis的输出结果封装

5.1 resultType配置结果类型

​ resultType属性可以指定结果集的类型,它支持基本类型和实体类类型。

​ CRUD案例中已经对此属性进行过应用了。

​ 需要注意的是,它和parameterType一样,如果注册过类型别名的,可以直接使用别名。没有注册过的必须使用全限定类名。

​ 同时,当是实体类名称是,还有一个要求,实体类中的属性名称必须和查询语句中的列名保持一致,否则无法实现封装。

5.1.1 基本类型示例

Dao接口:

/** 
* 查询总记录条数 
* @return 
*/ 
int findTotal();

映射配置:

<!-- 查询总记录条数 --> 
<select id="findTotal" resultType="int"> 
    select count(*) from user; 
</select>

5.1.2 实体类类型示例

Dao接口:

/** 
* 查询所有用户 
* @return 
*/ 
List<User> findAll();

映射配置:

<!-- 配置查询所有操作 --> 
<select id="findAll" resultType="user"> 
    select * from user
</select>

5.1.3 特殊情况

实体类中的属性名称必须和查询语句中的列名保持一致,否则无法实现封装。

修改映射配置

<!-- 使用别名查询 配置查询所有操作 -->
<select id="findAll" resultType="com..domain.User"> 
    select id as userId,username as userName,birthday as userBirthday, sex as userSex,address as userAddress from user
    </select>

如果我们的查询很多,都使用别名的话写起来岂不是很麻烦。

5.2 resultMap结果类型

​ resultMap标签可以建立查询的列名和实体类的属性名称不一致时建立对应关系。从而实现封装。

​ 在select标签中使用resultMap属性指定引用即可。同时resultMap可以实现将查询结果映射为复杂类型的pojo,比如在查询结果映射对象中包括pojo和list实现一对一查询和一对多查询。

5.2.1 定义resultMap

    <!-- 配置 查询结果的列名和实体类的属性名的对应关系 -->
    <resultMap id="userMap" type="user">
        <id property="id" column="id"></id>
        <!--非主键字段的对应-->
        <result property="username" column="username"></result>
        <result property="address" column="address"></result>
        <result property="sex" column="sex"></result>
        <result property="birthday" column="birthday"></result>
    </resultMap>

5.2.2 映射配置

<!-- 配置查询所有操作 --> 
<select id="findAll" resultMap="userMap"> 
    select * from user 
</select>

5.2.3 测试结果

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

6. Mybatis连接池与事务深入

6.1 Mybatis的连接池技术

6.1.1 Mybatis连接池的分类

在Mybatis中我们将它的数据源dataSource分为以下几类:

Mybatis将它自己的数据源分为三类:

​ UNPOOLED 不使用连接池的数据源

​ POOLED 使用连接池的数据源

​ JNDI 使用JNDI实现的数据源 具体结构如下:

​ 相应地,MyBatis内部分别定义了实现了java.sql.DataSource接口的UnpooledDataSource,PooledDataSource类来表示UNPOOLED、POOLED类型的数据源。

在这三种数据源中,我们一般采用的是POOLED数据源(很多时候我们所说的数据源就是为了更好的管理数据库连接,也就是我们所说的连接池技术)。

6.1.2 Mybatis中数据源的配置

我们的数据源配置就是在SqlMapConfig.xml文件中,具体配置如下: 
<!-- 配置数据源(连接池)信息 --> 
<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> 
MyBatis在初始化时,根据<dataSource>的type属性来创建相应类型的的数据源DataSource,即: 
    type=”POOLED”:MyBatis会创建PooledDataSource实例 
    type=”UNPOOLED” : MyBatis会创建UnpooledDataSource实例 
    type=”JNDI”:MyBatis会从JNDI服务上查找DataSource实例,然后返回使用

6.2 Mybatis的事务控制

6.2.1 JDBC中事务的回顾

在JDBC中我们可以通过手动方式将事务的提交改为手动方式,通过setAutoCommit()方法就可以调整。 通过JDK文档,我们找到该方法如下:

那么我们的Mybatis框架因为是对JDBC的封装,所以Mybatis框架的事务控制方式,本身也是用JDBC的setAutoCommit()方法来设置事务提交方式的。

6.2.2 Mybatis中事务提交方式

//Mybatis中事务的提交方式,本质上就是调用JDBC的setAutoCommit()来实现事务控制。 
    @Before
    public void init() throws IOException {
        //第一步:读取配置文件
        resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        //第二步:创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(resourceAsStream);
        //第三步:创建SqlSession
        //session = factory.openSession(true);//自动提交
        session = factory.openSession();
        //第四步:创建Dao接口的代理对象
        userDao = session.getMapper(UserDaoInterface.class);
    }

    @After
    public void destroy() throws IOException {
        //提交事务
        session.commit();
        //第六步:释放资源
        session.close();
        resourceAsStream.close();
    }

​ 这是我们的Connection的整个变化过程,通过分析我们能够发现之前的CUD操作过程中,我们都要手动进行事务的提交,原因是setAutoCommit()方法,在执行时它的值被设置为false了,所以我们在CUD操作中,必须通过sqlSession.commit()方法来执行提交操作。

6.2.3 Mybatis自动提交事务的设置

@Before
    public void init() throws IOException {
        //第一步:读取配置文件
        resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        //第二步:创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(resourceAsStream);
        //第三步:创建SqlSession
        session = factory.openSession(true);//自动提交
        //session = factory.openSession();
        //第四步:创建Dao接口的代理对象
        userDao = session.getMapper(UserDaoInterface.class);
    }

    @After
    public void destroy() throws IOException {
        //提交事务
        //session.commit();
        //第六步:释放资源
        session.close();
        resourceAsStream.close();
    }

​ 此时事务就设置为自动提交了,同样可以实现CUD操作时记录的保存。虽然这也是一种方式,但就编程而言,设置为自动提交方式为false再根据情况决定是否进行提交,这种方式更常用。因为我们可以根据业务情况来决定提交是否进行提交。

7. Mybatis的动态SQL语句

​ Mybatis的映射文件中,前面我们的SQL都是比较简单的,有些时候业务逻辑复杂时,我们的SQL是动态变化的,此时在前面的学习中我们的SQL就不能满足要求了。

参考的官方文档,描述如下:

7.1 动态SQL之 if 标签

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

7.1.1 持久层Dao接口

/** 
* 根据用户信息,查询用户列表 
* @param user 
* @return 
*/ 
List<User> findByUser(User user);

7.1.2 持久层Dao映射配置

<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 的作用~!

7.1.3 测试

@Test 
public void testFindByUser() { 
    User u = new User(); 
    u.setUsername("%王%"); 
    u.setAddress("%顺义%"); 
    //6.执行操作 
    List<User> users = userDao.findByUser(u); 
    for(User user : users) { 
        System.out.println(user); 
    } 
}

7.2 动态SQL之where标签

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

7.2.1 持久层Dao映射配置

    <select id="findUserByCondition" resultType="cn.msy.domain.User" parameterType="cn.msy.domain.User">
        select * from user
        <where>
            <if test="username != null">
                and username=#{username}
            </if>
            <if test="sex != null">
                and sex=#{sex}
            </if>
        </where>
    </select>

7.3 动态标签之foreach标签

7.3.1 需求

传入多个id查询用户信息,用下边两个sql实现:

SELECT * FROM USERS WHERE username LIKE '%张%' AND (id =10 OR id =89 OR id=16)

SELECT * FROM USERS WHERE username LIKE '%张%' AND id IN (10,89,16)

这样我们在进行范围查询时,就要将一个集合中的值,作为参数动态添加进来。 这样我们将如何进行参数的传递?

7.3.1.1 在QueryVo中加入一个List集合用于封装参数

package cn.msy.domain;

import java.util.List;


public class QueryVo {

    private User user;

    private List<Integer> ids;

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public List<Integer> getIds() {
        return ids;
    }

    public void setIds(List<Integer> ids) {
        this.ids = ids;
    }
}

7.3.2 持久层Dao接口

/** * 根据id集合查询用户 
* @param vo 
* @return 
*/ 
List<User> findInIds(QueryVo vo);

7.3.3 持久层Dao映射配置

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

7.3.4 测试

@Test 
public void testFindInIds() { 
    QueryVo vo = new QueryVo(); 
    List<Integer> ids = new ArrayList<Integer>(); 
    ids.add(41); 
    ids.add(42); 
    ids.add(43); 
    ids.add(46); 
    ids.add(57); 
    vo.setIds(ids); 
    //6.执行操作 
    List<User> users = userDao.findInIds(vo); 
    for(User user : users) { 
        System.out.println(user); 
    } 
}

8. Mybatis 多表查询之一对多

​ 本次案例主要以最为简单的用户和账户的模型来分析Mybatis 多表关系。用户为User 表,账户为Account表。一个用户(User)可以有多个账户(Account)。具体关系如下:

8.1 一对一查询(多对一)

需求

查询所有账户信息,关联查询下单用户信息。

注意:

因为一个账户信息只能供某个用户使用,所以从查询账户信息出发关联查询用户信息为一对一查询。如果从用户信息出发查询用户下的账户信息则为一对多查询,因为一个用户可以有多个账户。

8.1.1 方式一

8.1.1.1 定义账户信息的实体类

/**
*
* Title: Account
* Description: 账户的实体类
*/
package cn.msy.domain;

import java.io.Serializable;

public class Account implements Serializable {

    private Integer id;
    private Integer uid;
    private Double money;

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getUid() {
        return uid;
    }

    public void setUid(Integer uid) {
        this.uid = uid;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", uid=" + uid +
                ", money=" + money +
                '}';
    }
}

8.1.1.2 编写Sql语句

实现查询账户信息时,也要查询账户所对应的用户信息。

SELECT
    account.*, 
    user.username, 
    user.address 
FROM 
    account, 
    user 
WHERE account.uid = user.id

8.1.1.3 定义AccountUser类

​ 为了能够封装上面SQL语句的查询结果,定义 AccountCustomer类中要包含账户信息同时还要包含用户信息,所以我们要在定义AccountUser类时可以继承User类。

public class AccountUser extends Account {

    private String username;
    private String address;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return super.toString()+"        AccountUser{" +
                "username='" + username + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

8.1.1.4 定义账户的持久层Dao接口

public interface IAccountDao { 
    /** 
    * 查询所有账户,同时获取账户的所属用户名称以及它的地址信息 
    * @return 
    */ 
    List<AccountUser> findAll();
}

8.1.1.5 定义AccountDao.xml文件中的查询配置信息

  <!-- 查询所有 -->
    <select id="findAll" resultMap="accountUserMap">
        SELECT
    account.*, 
    user.username, 
    user.address 
FROM 
    account, 
    user 
WHERE account.uid = user.id
    </select>

定义专门的po类作为输出类型,其中定义了sql查询结果集所有的字段。此方法较为简单,企业中使用普遍。

8.1.2 方式二

​ 使用resultMap,定义专门的resultMap用于映射一对一查询结果。

​ 通过面向对象的(has a)关系可以得知,我们可以在Account类中加入一个User类的对象来代表这个账户是哪个用户的。

8.1.2.1 修改Account类

在Account类中加入User类的对象作为Account类的一个属性。

package cn.msy.domain;

import java.io.Serializable;

public class Account implements Serializable {

    private Integer id;
    private Integer uid;
    private Double money;

    //多对一(mybaytis中称之为一对一)的映射,一个账户只能属于一个用户
    //从表实体应该包含一个主表实体的对象引用
    private User user;

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getUid() {
        return uid;
    }

    public void setUid(Integer uid) {
        this.uid = uid;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", uid=" + uid +
                ", money=" + money +
                '}';
    }
}

8.1.2.2 修改AccountDao接口中的方法

public interface IAccountDao { 
    /** 
    * 查询所有账户,同时获取账户的所属用户名称以及它的地址信息 
    * @return 
    */ 
    List<Account> findAll();
}
注意:第二种方式,将返回值改 为了Account类型。 
   因为Account类中包含了一个User类的对象,它可以封装账户所对应的用户信息。

8.1.2.3 重新定义AccountDao.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="com..dao.IAccountDao">

    <!-- 定义封装account和user的resultMap -->
    <resultMap id="accountUserMap" type="account">
        <id property="id" column="aid"></id>
        <result property="uid" column="uid"></result>
        <result property="money" column="money"></result>
        <!-- 一对一的关系映射:配置封装user的内容-->
        <association property="user" column="uid" javaType="user">
            <id property="id" column="id"></id>
            <result column="username" property="username"></result>
            <result column="address" property="address"></result>
            <result column="sex" property="sex"></result>
            <result column="birthday" property="birthday"></result>
        </association>
    </resultMap>

    <!-- 查询所有 -->
    <select id="findAll" resultMap="accountUserMap">
       SELECT
    account.*, 
    user.username, 
    user.address 
FROM 
    account, 
    user 
WHERE account.uid = user.id
    </select>
</mapper>

8.1.2.4 在AccountTest类中加入测试方法

@Test 
public void testFindAll() {
    List<Account> accounts = accountDao.findAll(); 
    for(Account au : accounts) { 
        System.out.println(au); 
        System.out.println(au.getUser()); 
    } 
}

8.2 一对多查询

需求:

​ 查询所有用户信息及用户关联的账户信息。

分析:

​ 用户信息和他的账户信息为一对多关系,并且查询过程中如果用户没有账户信息,此时也要将用户信息查询出来,我们想到了左外连接查询比较合适。

8.2.1 编写SQL语句

SELECT 
    u.*, 
    acc.id id, 
    acc.uid, 
    acc.money 
FROM 
    user u LEFT JOIN account acc ON u.id = acc.uid

8.2.2 User类加入List Account

public class User implements Serializable {

    private Integer id;
    private String username;
    private String address;
    private String sex;
    private Date birthday;

    //一对多关系映射:主表实体应该包含从表实体的集合引用
    private List<Account> accounts;

    public List<Account> getAccounts() {
        return accounts;
    }

    public void setAccounts(List<Account> accounts) {
        this.accounts = accounts;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", address='" + address + '\'' +
                ", sex='" + sex + '\'' +
                ", birthday=" + birthday +
                '}';
    }
}

8.2.3 用户持久层Dao接口中加入查询方法

/** 
* 查询所有用户,同时获取出每个用户下的所有账户信息 
* @return 
*/ 
List<User> findAll();

8.2.4 用户持久层Dao映射文件配置

<?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..dao.IUserDao">

    <!-- 定义User的resultMap-->
    <resultMap id="userAccountMap" type="user">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="address" column="address"></result>
        <result property="sex" column="sex"></result>
        <result property="birthday" column="birthday"></result>
        <!-- 配置user对象中accounts集合的映射 -->
        <collection property="accounts" ofType="account">
            <id column="aid" property="id"></id>
            <result column="uid" property="uid"></result>
            <result column="money" property="money"></result>
        </collection>
    </resultMap>

    <!-- 查询所有 -->
    <select id="findAll" resultMap="userAccountMap">
       SELECT 
    u.*, 
    acc.id id, 
    acc.uid, 
    acc.money 
FROM 
    user u LEFT JOIN account acc ON u.id = acc.uid
    </select>
</mapper>

collection 
    部分定义了用户关联的账户信息。表示关联查询结果集 
property="aoocunts": 
    关联查询的结果集存储在User对象的上哪个属性。 
ofType="account": 
指定关联查询的结果集中的对象类型即List中的对象类型。此处可以使用别名,也可以使用全限定名。

9. Mybatis 多表查询之多对多

9.1 实现Role 到User 多对多

多对多关系其实我们看成是双向的一对多关系。

用户角色中间表

9.1.2 业务要求及实现SQL

需求:

​ 实现查询所有对象并且加载它所分配的用户信息。

分析:

​ 查询角色我们需要用到Role表,但角色分配的用户的信息我们并不能直接找到用户信息,而是要通过中间表(USER_ROLE表)才能关联到用户信息。

​ 下面是实现的SQL语句:

SELECT 
r.*,
u.id uid,
u.username username, 
u.birthday birthday, 
u.sex sex, 
u.address address 
FROM ROLE r
JOIN USER_ROLE ur ON ( r.id = ur.rid) 
JOIN USER u ON (ur.uid = u.id);

9.1.3 编写角色实体类

public class Role implements Serializable {

    private Integer roleId;
    private String roleName;
    private String roleDesc;

    //多对多的关系映射:一个角色可以赋予多个用户
    private List<User> users;

    public List<User> getUsers() {
        return users;
    }

    public void setUsers(List<User> users) {
        this.users = users;
    }

    public Integer getRoleId() {
        return roleId;
    }

    public void setRoleId(Integer roleId) {
        this.roleId = roleId;
    }

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public String getRoleDesc() {
        return roleDesc;
    }

    public void setRoleDesc(String roleDesc) {
        this.roleDesc = roleDesc;
    }

    @Override
    public String toString() {
        return "Role{" +
                "roleId=" + roleId +
                ", roleName='" + roleName + '\'' +
                ", roleDesc='" + roleDesc + '\'' +
                '}';
    }
}

9.1.4 编写Role持久层接口

public interface IRoleDao { 
    /** 
    * 查询所有角色 
    * @return 
    */ 
    List<Role> findAll(); 
}

9.1.5 编写映射文件

<?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.msy.dao.RoleDao">
    <resultMap id="rolemap" type="role">
        <id property="roleId" column="rid"></id>
        <!--非主键字段的对应-->
        <result property="roleName" column="role_name"></result>
        <result property="roleDesc" column="role_desc"></result>
        <result property="roleDesc" column="role_desc"></result>
        <collection property="users" ofType="user">
            <id property="id" column="id"></id>
            <!--非主键字段的对应-->
            <result property="username" column="username"></result>
            <result property="address" column="address"></result>
            <result property="sex" column="sex"></result>
            <result property="birthday" column="birthday"></result>
        </collection>
    </resultMap>
    <select id="findAll" resultMap="rolemap">
--         select * from role,user,user_role where role.id=user_role.rid and user.id=user_role.uid

         select u.*,r.id rid,r.ROLE_NAME,r.ROLE_DESC from role r
         left join user_role ur
         on r.id=ur.rid
         left join user u
         on u.id=ur.uid
    </select>
</mapper>

9.2 实现User到Role的多对多

9.2.1 User到Role的多对多

​ 从User出发,我们也可以发现一个用户可以具有多个角色,这样用户到角色的关系也还是一对多关系。这样我们就可以认为User与Role的多对多关系,可以被拆解成两个一对多关系来实现。

9.2.2 实现User到Role的一对多查询

编写sql

         select u.*,r.id rid,r.ROLE_NAME,r.ROLE_DESC from user u
         left join user_role ur
         on u.id=ur.uid
         left join role r
         on ur.rid=r.id

改写user类

package cn.msy.domain;


import java.util.Date;
import java.util.List;

public class User {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
    private List<Role> roles;

    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", birthday=" + birthday +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

定义dao

    /**
     * 查询所有操作
     * @return
     */
    List<User> findAll();

编写映射文件

<?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.msy.dao.UserDaoInterface">

    <!-- 配置 查询结果的列名和实体类的属性名的对应关系 -->
    <resultMap id="userMap" type="cn.msy.domain.User">
        <id property="id" column="id"></id>
        <!--非主键字段的对应-->
        <result property="username" column="username"></result>
        <result property="address" column="address"></result>
        <result property="sex" column="sex"></result>
        <result property="birthday" column="birthday"></result>
        <collection property="roles" ofType="role">
            <id property="roleId" column="rid"></id>
            <!--非主键字段的对应-->
            <result property="roleName" column="role_name"></result>
            <result property="roleDesc" column="role_desc"></result>
        </collection>
    </resultMap>
    <select id="findAll" resultMap="userMap">
                select u.*,r.id rid,r.ROLE_NAME,r.ROLE_DESC from user u
         left join user_role ur
         on u.id=ur.uid
         left join role r
         on ur.rid=r.id
    </select>
</mapper>

10. Mybatis延迟加载策略

​ 实际开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的账户信息。此时就是我们所说的延迟加载。

10.1 何为延迟加载?

延迟加载:

​ 就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载. 好处:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。

坏处:

因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。

10.2 实现需求

需求:

​ 查询账户(Account)信息并且关联查询用户(User)信息。如果先查询账户(Account)信息即可满足要求,当我们需要查询用户(User)信息时再查询用户(User)信息。把对用户(User)信息的按需去查询就是延迟加载。

​ 使用了resultMap来实现一对一,一对多,多对多关系的操作。主要是通过association、collection实现一对一及一对多映射。association、collection具备延迟加载功能。

10.3 使用assocation实现延迟加载

需求:

查询账户信息同时查询用户信息。

10.3.1 账户的持久层DAO接口

package cn.msy.dao;
import cn.msy.domain.Account;
import java.util.List;

public interface IAccountDao {

    /**
     * 查询所有账户,同时还要获取到当前账户的所属用户信息
     * @return
     */
    List<Account> findAll();

}

10.3.2 账户的持久层映射文件

<?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.msy.dao.IAccountDao">

    <!-- 定义封装account和user的resultMap -->
    <resultMap id="accountUserMap" type="account">
        <id property="id" column="id"></id>
        <result property="uid" column="uid"></result>
        <result property="money" column="money"></result>
        <!-- 一对一的关系映射:配置封装user的内容
        select属性指定的内容:查询用户的唯一标识:
        column属性指定的内容:用户根据id查询时,所需要的参数的值
        -->
        <association property="user" column="uid" javaType="user" select="cn.msy.dao.IUserDao.findById"></association>
    </resultMap>

    <!-- 查询所有 -->
    <select id="findAll" resultMap="accountUserMap">
        select * from account
    </select>
</mapper>
select: 填写我们要调用的 select 映射的 id 
column: 填写我们要传递给 select 映射的参数

10.3.3 用户的持久层接口和映射文件

package cn.msy.dao;

import cn.msy.domain.User;
import cn.msy.domain.User;
import java.util.List;

public interface IUserDao {

    /**
     * 根据id查询用户信息
     * @param userId
     * @return
     */
    User findById(Integer userId);
}
<?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.msy.dao.IUserDao">
    <!-- 根据id查询用户 -->
    <select id="findById" parameterType="INT" resultType="user">
        select * from user where id = #{uid}
    </select>
</mapper>

10.3.4 开启Mybatis的延迟加载策略

进入Mybaits的官方文档,找到settings的说明信息:

我们需要在Mybatis的配置文件SqlMapConfig.xml文件中添加延迟加载的配置。

<!-- 开启延迟加载的支持 -->
<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/> 
</settings>

10.3.5 编写测试只查账户信息不查用户信息。

package cn.smy.test;

import cn.msy.dao.IAccountDao;
import cn.msy.domain.Account;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.InputStream;
import java.util.List;

/**
 * @author 黑马程序员
 * @Company http://www.ithiema.com
 */
public class AccountTest {

    private InputStream in;
    private SqlSession sqlSession;
    private IAccountDao accountDao;

    @Before//用于在测试方法执行之前执行
    public void init() throws Exception {
        //1.读取配置文件,生成字节输入流
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.获取SqlSessionFactory
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //3.获取SqlSession对象
        sqlSession = factory.openSession(true);
        //4.获取dao的代理对象
        accountDao = sqlSession.getMapper(IAccountDao.class);
    }

    @After//用于在测试方法执行之后执行
    public void destroy() throws Exception {
        //提交事务
        // sqlSession.commit();
        //6.释放资源
        sqlSession.close();
        in.close();
    }

    /**
     * 测试查询所有
     */
    @Test
    public void testFindAll() {
        List<Account> accounts = accountDao.findAll();
/*        for (Account account : accounts) {
            System.out.println("--------每个account的信息------------");
            System.out.println(account);
            System.out.println(account.getUser());
        }*/
    }


}

结果:

10.4 使用Collection实现延迟加载

​ 同样我们也可以在一对多关系配置的collection结点中配置延迟加载策略。

​ collection结点中也有select属性,column属性。

​ 需求:

​ 完成加载用户对象时,查询该用户所拥有的账户信息。

10.4.1 在User实体类中加入ListAccount属性

package cn.msy.domain;

import java.io.Serializable;
import java.util.Date;
import java.util.List;
public class User implements Serializable {

    private Integer id;
    private String username;
    private String address;
    private String sex;
    private Date birthday;

    //一对多关系映射:主表实体应该包含从表实体的集合引用
    private List<Account> accounts;

    public List<Account> getAccounts() {
        return accounts;
    }

    public void setAccounts(List<Account> accounts) {
        this.accounts = accounts;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", address='" + address + '\'' +
                ", sex='" + sex + '\'' +
                ", birthday=" + birthday +
                '}';
    }
}

10.4.2 编写用户和账户持久层接口的方法

package cn.msy.dao;

import cn.msy.domain.User;
import cn.msy.domain.User;
import java.util.List;

public interface IUserDao {

    /**
     * 查询所有用户,同时获取到用户下所有账户的信息
     * @return
     */
    List<User> findAll();

    /**
     * 根据id查询用户信息
     * @param userId
     * @return
     */
    User findById(Integer userId);


}

10.4.3 编写用户持久层映射配置

<?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.msy.dao.IUserDao">

    <!-- 定义User的resultMap-->
    <resultMap id="userAccountMap" type="user">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="address" column="address"></result>
        <result property="sex" column="sex"></result>
        <result property="birthday" column="birthday"></result>
        <!-- 配置user对象中accounts集合的映射 -->
        <collection property="accounts" ofType="account" select="cn.msy.dao.IAccountDao.findAccountByUid" column="id"></collection>
    </resultMap>

    <!-- 查询所有 -->
    <select id="findAll" resultMap="userAccountMap">
        select * from user
    </select>

    <!-- 根据id查询用户 -->
    <select id="findById" parameterType="INT" resultType="user">
        select * from user where id = #{uid}
    </select>
</mapper>

collection标签: 
    主要用于加载关联的集合对象 
select属性: 
    用于指定查询account列表的sql语句,所以填写的是该sql映射的id 
column属性: 
    用于指定select属性的sql语句的参数来源,上面的参数来自于user的id列,所以就写成id这一个字段名了

10.4.4 编写账户持久层映射配置

<!-- 根据用户id查询账户信息 --> 
<select id="findByUid" resultType="account" parameterType="int"> 
    select * from account where uid = #{uid} 
</select>

10.4.5 测试只加载用户信息

package cn.smy.test;

import cn.msy.dao.IUserDao;
import cn.msy.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.InputStream;
import java.util.List;

public class UserTest {

    private InputStream in;
    private SqlSession sqlSession;
    private IUserDao userDao;

    @Before//用于在测试方法执行之前执行
    public void init()throws Exception{
        //1.读取配置文件,生成字节输入流
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.获取SqlSessionFactory
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //3.获取SqlSession对象
        sqlSession = factory.openSession(true);
        //4.获取dao的代理对象
        userDao = sqlSession.getMapper(IUserDao.class);
    }

    @After//用于在测试方法执行之后执行
    public void destroy()throws Exception{
        //提交事务
        // sqlSession.commit();
        //6.释放资源
        sqlSession.close();
        in.close();
    }
    /**
     * 测试查询所有
     */
    @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());
//        }
    }
}

测试结果如下:

11. Mybatis缓存

11.1 Mybatis一级缓存

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

​ Mybatis中缓存分为一级缓存,二级缓存。

11.1.1 证明一级缓存的存在

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

11.1.1.1 编写用户持久层Dao接口

public interface IUserDao { 
    /** 
    * 根据id查询 
    * @param userId 
    * @return 
    */ 
    User findById(Integer userId); }

11.1.1.2 编写用户持久层映射文件

<mapper namespace="com..dao.IUserDao"> 
    <!-- 根据id查询 --> 
    <select id="findById" resultType="User" parameterType="int" useCache="true"> 
        select * from user where id = #{uid} 
    </select> 
</mapper>

11.1.1.3 编写测试方法

package com..test;

import com..dao.IUserDao;
import com..domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.InputStream;

public class UserTest {

    private InputStream in;
    private  SqlSessionFactory factory;
    private SqlSession sqlSession;
    private IUserDao userDao;

    @Before//用于在测试方法执行之前执行
    public void init()throws Exception{
        //1.读取配置文件,生成字节输入流
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.获取SqlSessionFactory
        factory = new SqlSessionFactoryBuilder().build(in);
        //3.获取SqlSession对象
        sqlSession = factory.openSession(true);
        //4.获取dao的代理对象
        userDao = sqlSession.getMapper(IUserDao.class);
    }

    @After//用于在测试方法执行之后执行
    public void destroy()throws Exception{
        //提交事务
        // sqlSession.commit();
        //6.释放资源
        sqlSession.close();
        in.close();
    }

    /**
     * 测试一级缓存
     */
    @Test
    public void testFirstLevelCache(){
        User user1 = userDao.findById(41);
        System.out.println(user1);

        User user2 = userDao.findById(41);
        System.out.println(user2);

        System.out.println(user1 == user2);
    }
}

测试结果如下:

​ 我们可以发现,虽然在上面的代码中我们查询了两次,但最后只执行了一次数据库操作,这就是Mybatis提供给我们的一级缓存在起作用了。因为一级缓存的存在,导致第二次查询id为41的记录时,并没有发出sql语句从数据库中查询数据,而是从一级缓存中查询。

11.1.2 一级缓存的分析

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

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

​ 得到用户信息,将用户信息存储到一级缓存中。

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

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

11.1.3 测试一级缓存的清空

public class UserTest {

    private InputStream in;
    private  SqlSessionFactory factory;
    private SqlSession sqlSession;
    private IUserDao userDao;

    @Before//用于在测试方法执行之前执行
    public void init()throws Exception{
        //1.读取配置文件,生成字节输入流
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.获取SqlSessionFactory
        factory = new SqlSessionFactoryBuilder().build(in);
        //3.获取SqlSession对象
        sqlSession = factory.openSession(true);
        //4.获取dao的代理对象
        userDao = sqlSession.getMapper(IUserDao.class);
    }

    @After//用于在测试方法执行之后执行
    public void destroy()throws Exception{
        //提交事务
        // sqlSession.commit();
        //6.释放资源
        sqlSession.close();
        in.close();
    }

    /**
     * 测试一级缓存
     */
    @Test
    public void testFirstLevelCache(){
        User user1 = userDao.findById(41);
        System.out.println(user1);
        
        // sqlSession.close(); 
        //再次获取SqlSession对象 
        // sqlSession = factory.openSession(); 
        
        sqlSession.clearCache();//此方法也可以清空缓存 
        userDao = sqlSession.getMapper(IUserDao.class);

        User user2 = userDao.findById(41);
        System.out.println(user2);

        System.out.println(user1 == user2);
    }
    
       /**
     * 测试缓存的同步
     */
    @Test
    public void testClearlCache(){
        //1.根据id查询用户
        User user1 = userDao.findById(41);
        System.out.println(user1);

        //2.更新用户信息
        user1.setUsername("update user clear cache");
        user1.setAddress("北京市海淀区");
        userDao.updateUser(user1);

        //3.再次查询id为41的用户
        User user2 = userDao.findById(41);
        System.out.println(user2);

        System.out.println(user1 == user2);
    }
}

11.2 Mybatis二级缓存

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

11.2.1 二级缓存结构图

​ 首先开启mybatis的二级缓存。

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

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

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

11.2.2 二级缓存的开启与关闭

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

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

11.2.2.2 第二步:配置相关的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..dao.IUserDao"> 
        <!-- 开启二级缓存的支持 -->
        <cache></cache> 
    </mapper>

11.2.2.3 第三步:配置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,禁用二级缓存。

11.2.3 二级缓存测试

package com..test;

import com..dao.IUserDao;
import com..domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.InputStream;


public class SecondLevelCacheTest {

    private InputStream in;
    private  SqlSessionFactory factory;

    @Before//用于在测试方法执行之前执行
    public void init()throws Exception{
        //1.读取配置文件,生成字节输入流
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.获取SqlSessionFactory
        factory = new SqlSessionFactoryBuilder().build(in);

    }

    @After//用于在测试方法执行之后执行
    public void destroy()throws Exception{
        in.close();
    }

    /**
     * 测试一级缓存
     */
    @Test
    public void testFirstLevelCache(){
        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语句,所以此时的数据就只能是来自于我们所说的二级缓存。

11.2.4 二级缓存注意事项

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

12. Mybatis注解开发

​ 这几年来注解开发越来越流行,Mybatis也可以使用注解开发方式,这样我们就可以减少编写Mapper映射文件了。本次我们先围绕一些基本的 CRUD来学习,再学习复杂映射关系及延迟加载。

12.1 mybatis的常用注解说明

@Insert:实现新增

@Update:实现更新

@Delete:实现删除

@Select:实现查询

@Result:实现结果集封装

@Results:可以与@Result一起使用,封装多个结果集

@ResultMap:实现引用@Results定义的封装

@One:实现一对一结果集封装

@Many:实现一对多结果集封装

@SelectProvider: 实现动态SQL映射

@CacheNamespace:实现注解二级缓存的使用

12.2 使用Mybatis注解实现基本CRUD

12.2.1 编写实体类

package cn.msy.domain;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

public class User implements Serializable {

    private Integer userId;
    private String userName;
    private String userAddress;
    private String userSex;
    private Date userBirthday;

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserAddress() {
        return userAddress;
    }

    public void setUserAddress(String userAddress) {
        this.userAddress = userAddress;
    }

    public String getUserSex() {
        return userSex;
    }

    public void setUserSex(String userSex) {
        this.userSex = userSex;
    }

    public Date getUserBirthday() {
        return userBirthday;
    }

    public void setUserBirthday(Date userBirthday) {
        this.userBirthday = userBirthday;
    }
}

注意: 此处故意和数据库表的列名不一致。

12.2.2 使用注解方式开发持久层接口

package cn.msy.dao;

import cn.msy.domain.User;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.mapping.FetchType;

import java.util.List;

/**
 * 在mybatis中针对,CRUD一共有四个注解
 *
 * @Select @Insert @Update @Delete
 */
public interface IUserDao {

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

    /**
     * 保存用户
     *
     * @param user
     */
    @Insert("insert into user(username,address,sex,birthday)values(#{userName},#{userAddress},#{userSex},#{userBirthday})")
    void saveUser(User user);

    /**
     * 更新用户
     *
     * @param user
     */
    @Update("update user set username=#{username},sex=#{sex},birthday=#{birthday},address=#{address} where id=#{id}")
    void updateUser(User user);

    /**
     * 删除用户
     *
     * @param userId
     */
    @Delete("delete from user where id=#{uid} ")
    void deleteUser(Integer userId);

    /**
     * 根据id查询用户
     *
     * @param userId
     * @return
     */
    @Select("select * from user  where id=#{id} ")
    @ResultMap("userMap")
    User findById(Integer userId);

    /**
     * 根据用户名称模糊查询
     *
     * @param username
     * @return
     */
//    @Select("select * from user where username like #{username} ")
    @Select("select * from user where username like '%${value}%' ")
    List<User> findUserByName(String username);

    /**
     * 查询总用户数量
     *
     * @return
     */
    @Select("select count(*) from user ")
    int findTotalUser();
}
通过注解方式,就不需要再去编写UserDao.xml 映射文件了。

12.2.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="jdbcConfig.properties"></properties>

    <!--    配置别名-->
    <typeAliases>
        <package name="cn.msy.domain"></package>
    </typeAliases>

    <environments default="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>

    <!--    指定带有注解的dao接口的所在位置-->
    <mappers>
        <!-- 配置dao接口的位置,它有两种方式 
第一种:使用mapper标签配置class属性 
第二种:使用package标签,直接指定dao接口所在的包 
-->
        <package name="cn.msy.dao"></package>
    </mappers>
</configuration>

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

​ 实现复杂关系映射之前我们可以在映射文件中通过配置resultMap来实现,在使用注解开发时我们需要借助@Results注解,@Result注解,@One注解,@Many注解。

12.3.1 复杂关系映射的注解说明

    @Results注解     
    代替的是标签<resultMap> 
    该注解中可以使用单个@Result注解,也可以使用@Result集合 
    @Results({@Result(),@Result()})或@Results(@Result())
    
    @Resutl注解
    代替了 <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=""))

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

需求:

​ 加载账户信息时并且加载该账户的用户信息,根据情况可实现延迟加载。(注解方式实现)

12.3.2.1 添加User实体类及Account实体类

package cn.msy.domain;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

public class User implements Serializable {

    private Integer userId;
    private String userName;
    private String userAddress;
    private String userSex;
    private Date userBirthday;

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserAddress() {
        return userAddress;
    }

    public void setUserAddress(String userAddress) {
        this.userAddress = userAddress;
    }

    public String getUserSex() {
        return userSex;
    }

    public void setUserSex(String userSex) {
        this.userSex = userSex;
    }

    public Date getUserBirthday() {
        return userBirthday;
    }

    public void setUserBirthday(Date userBirthday) {
        this.userBirthday = userBirthday;
    }
}

注意: 此处故意和数据库表的列名不一致。
package cn.msy.domain;

import java.io.Serializable;

public class Account implements Serializable {

    private Integer id;
    private Integer uid;
    private Double money;

    //多对一(mybaytis中称之为一对一)的映射,一个账户只能属于一个用户
    //从表实体应该包含一个主表实体的对象引用
    private User user;

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getUid() {
        return uid;
    }

    public void setUid(Integer uid) {
        this.uid = uid;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", uid=" + uid +
                ", money=" + money +
                '}';
    }
}

12.3.2.2 添加账户的持久层接口并使用注解配置

package cn.msy.dao;

import cn.msy.domain.Account;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.mapping.FetchType;

import java.util.List;

public interface IAccountDao {

    /**
     * 查询所有账户,获取每个账户的所属用户信息
     * @return
     */
    @Select("select * from account")
    @Results(id="accountMap",value = {
            @Result(id=true,column = "id",property = "id"),
            @Result(column = "uid",property = "uid"),
            @Result(column = "money",property = "money"),
            @Result(property = "user",column = "uid",one = @One(select = "cn.msy.dao.IUserDao.findById",fetchType = FetchType.LAZY))
    })
    List<Account> findAll();
}

12.3.2.3 添加用户的持久层接口并使用注解配置

package cn.msy.dao;

import cn.msy.domain.User;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.mapping.FetchType;

import java.util.List;

/**
 * 在mybatis中针对,CRUD一共有四个注解
 *
 * @Select @Insert @Update @Delete
 */
public interface IUserDao {

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

    /**
     * 根据id查询用户
     *
     * @param userId
     * @return
     */
    @Select("select * from user  where id=#{id} ")
    @ResultMap("userMap")
    User findById(Integer userId);
}

12.3.2.4 测试一对一关联及延迟加载

    /**
     * 测试查询所有
     */
    @Test
    public void testFindAll(){
        List<Account> all = accountDao.findAll();

/*        for (Account account : all) {
            System.out.println(account);
            System.out.println(account.getUser());
        }*/
    }

}

12.3.3 使用注解实现一对多复杂关系映射

需求:

​ 查询用户信息时,也要查询他的账户列表。使用注解方式实现。

分析:

​ 一个用户具有多个账户信息,所以形成了用户(User)与账户(Account)之间的一对多关系。

12.3.3.1 User实体类加入ListAccount

package cn.msy.domain;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

public class User implements Serializable {

    private Integer userId;
    private String userName;
    private String userAddress;
    private String userSex;
    private Date userBirthday;

    //一对多关系映射:一个用户对应多个账户
    private List<Account> accounts;

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                ", userAddress='" + userAddress + '\'' +
                ", userSex='" + userSex + '\'' +
                ", userBirthday=" + userBirthday +
                '}';
    }

    public List<Account> getAccounts() {
        return accounts;
    }

    public void setAccounts(List<Account> accounts) {
        this.accounts = accounts;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserAddress() {
        return userAddress;
    }

    public void setUserAddress(String userAddress) {
        this.userAddress = userAddress;
    }

    public String getUserSex() {
        return userSex;
    }

    public void setUserSex(String userSex) {
        this.userSex = userSex;
    }

    public Date getUserBirthday() {
        return userBirthday;
    }

    public void setUserBirthday(Date userBirthday) {
        this.userBirthday = userBirthday;
    }
}

12.3.3.2 编写用户的持久层接口并使用注解配置

package cn.msy.dao;

import cn.msy.domain.User;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.mapping.FetchType;

import java.util.List;

/**
 * 在mybatis中针对,CRUD一共有四个注解
 *
 * @Select @Insert @Update @Delete
 */
public interface IUserDao {

    /**
     * 查询所有用户
     *
     * @return
     */
    @Select("select * from user")
    @Results(id = "userMap", value = {
            @Result(id = true, column = "id", property = "userId"),
            @Result(column = "username", property = "userName"),
            @Result(column = "address", property = "userAddress"),
            @Result(column = "sex", property = "userSex"),
            @Result(column = "birthday", property = "userBirthday"),
            @Result(property = "accounts",column = "id",many = @Many(fetchType = FetchType.LAZY,select = "cn.msy.dao.IAccountDao.findAccountById"))
    })
    List<User> findAll();

}
@Many:
    相当于<collection>的配置 
    select属性:代表将要执行的sql语句 
    fetchType属性:代表加载方式,一般如果要延迟加载都设置为LAZY的值

12.3.3.3 编写账户的持久层接口并使用注解配置

package cn.msy.dao;

import cn.msy.domain.Account;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.mapping.FetchType;

import java.util.List;

public interface IAccountDao {
    @Select("select * from account where uid=#{id}")
    List<Account> findAccountById(Integer id);
}

12.3.3.4 添加测试方法

    /**
     * 测试查询所有
     */
    @Test
    public void testFindAll(){
        List<User> all = userDao.findAll();
        
/*        for (User user : all) {
            System.out.println(user);
            user.getAccounts().stream().forEach(account -> {
                System.out.println(account);
            });
        }*/
    }

12.4 mybatis基于注解的二级缓存

12.4.1 在SqlMapConfig中开启二级缓存支持

<!-- 配置二级缓存 --> 
<settings> 
    <!-- 开启二级缓存的支持 --> 
    <setting name="cacheEnabled" value="true"/> 
</settings>

12.4.2 在持久层接口中使用注解配置二级缓存

/** 
* Description: 用户的持久层接口
*/ 
@CacheNamespace(blocking=true)//mybatis基于注解方式实现配置二级缓存
public interface IUserDao {}

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

发表评论

email
web

全部评论 (暂无评论)

info 还没有任何评论,你来说两句呐!