mybatis 入门

mybatis

mybatis 是一个优秀的基于 java 的持久层框架,它内部封装了 jdbc,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。
mybatis 通过 xml注解的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statementsql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。
采用 ORM 思想解决了实体和数据库映射的问题,对 jdbc 进行了封装,屏蔽了 jdbc api 底层访问细节,使我们不用与 jdbc api 打交道,就可以完成对数据库的持久化操作。

jdbc问题

1、数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
2、Sql 语句在代码中硬编码,造成代码不易维护,实际应用 sql 变化的可能较大,sql 变动需要改变 java 代码。
3、使用 preparedStatement 向占有位符号传参数存在硬编码,因为 sql 语句的 where 条件不一定,可能多也可能少,修改 sql 还要修改代码,系统不易维护。
4、对结果集解析存在硬编码(查询列名),sql 变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成 pojo 对象解析比较方便。

搭建 Mybatis 开发环境

创建一个 maven 项目, 在 pom.xml 中配置 mybatis 的坐标

    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.17</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

编写一个 User 实体类

使用 Alt+Insert 生成 get, set, toString 方法

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

编写持久层接口 UserDao

public interface UserDao {
    List<User> findAll();
}

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

要求

  • 创建位置:必须和持久层接口在相同的包中。
  • 名称:必须以持久层接口名称命名文件名,扩展名是.xml

UserDao.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.example.dao.UserDao">
    <!--    配置查询所有-->
    <select id="findAll" resultType="com.example.domain.User">
        select * from user ;
    </select>
</mapper>

编写 SqlMapConfig.xml 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<!--mybatis的主配置文件-->
<configuration>
<!--    配置环境-->
    <environments default="mysql">
<!--        配置 xx 的环境, xx是default=""中的值-->
        <environment id="mysql">
<!--            配置事务类型-->
            <transactionManager type="JDBC"/>
<!--            配置数据源(连接池)-->
            <dataSource type="POOLED">
<!--            配置数据库的基本信息-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://120.39.63.88:3306/test3"/>
                <property name="username" value="test"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>


<!--    指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件 -->
    <mappers>
        <mapper resource="com/example/dao/UserDao.xml"/>
    </mappers>
</configuration>

编写测试类

import com.example.dao.UserDao;
import com.example.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 MybatisTestDemo {
    public static void main(String[] args) throws IOException {
//1.读取配置文件
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建 SqlSessionFactory 的构建者对象
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3.使用构建者创建工厂对象 SqlSessionFactory
        SqlSessionFactory factory = builder.build(in);
//4.使用 SqlSessionFactory 生产 SqlSession 对象
        SqlSession session = factory.openSession();
//5.使用 SqlSession 创建 dao 接口的代理对象
        UserDao userDao = session.getMapper(UserDao.class);
//6.使用代理对象执行查询所有方法
        List<User> users = userDao.findAll();
        for (User user : users) {
            System.out.println(user);
        }
//7.释放资源
        session.close();
        in.close();
    }
}

基于注解的开发

在持久层接口中添加注解

import org.apache.ibatis.annotations.Select;


public interface UserDao {
    
    @Select("select * from user")
    List<User> findAll();
}

修改 SqlMapConfig.xml

使用的是 class

<!--    指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件 -->
    <mappers>
<!--        <mapper resource="com/example/dao/UserDao.xml"/>-->
        <mapper class="com.example.dao.UserDao"/>
    </mappers>

注意事项

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

CRUD

1、持久层接口和持久层接口的映射配置必须在相同的包下
2、持久层映射配置中 mapper 标签的 namespace 属性取值必须是持久层接口的全限定类名
3、SQL 语句的配置标签 <select> , <insert> , <delete> , <update>id 属性必须和持久层接口的方法名相同。

根据 ID 查询

在持久层接口中添加 findById 方法

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

配置映射文件 UserDao.xml

    <!--    根据id查询信息-->
    <select id="findById" parameterType="int" resultType="com.example.domain.User">
        select * from user where id=#{id};
    </select>
  • resultType 属性:用于指定结果集的类型。
  • parameterType 属性:用于指定传入参数的类型。
  • sql 语句中使用 #{} 字符:它代表占位符,相当于原来 jdbc 部分所学的?,都是用于执行语句时替换实际的数据。具体的数据是由 #{} 里面的内容决定的。
  • #{}中内容的写法:由于数据类型是基本类型,所以此处可以随意写。

测试

public class MybatisTest {
    InputStream resourceAsStream;
    SqlSession sqlSession;
    UserDao userDao;

    @Before
    public void init() throws Exception {
        //        1. 读取配置文件
        resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");

//        2. 创建 SqlSessionFactory 工厂
        SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = factoryBuilder.build(resourceAsStream);

//        3. 使用工厂生产 SqlSession 对象
        sqlSession = sqlSessionFactory.openSession();

//        4. 使用 SqlSession 创建 Dao 的代理对象
        userDao = sqlSession.getMapper(UserDao.class);
    }


    @After
    public void destroy() throws Exception {
        // 事务
        sqlSession.commit();

        //        6. 释放资源
        sqlSession.close();
        resourceAsStream.close();
    }

    @Test
    public void testfindById() {
        User user = userDao.findById(49);
        System.out.println("user = " + user);
    }
}

添加用户

在持久层接口中添加 findById 方法

com.example.dao.UserDao

    int saveUser(User user);

配置映射文件 UserDao.xml

    <!--    保存用户-->
    <insert id="saveUser" parameterType="com.example.domain.User">
        insert into user (username, address, sex, birthday) values (#{username}, #{address}, #{sex}, #{birthday});
        -- 配置插入操作后获取 自增 id
        <selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
            select last_insert_id();
        </selectKey>
    </insert>
  • parameterType 属性:代表参数的类型,因为我们要传入的是一个类的对象,所以类型就写类的全名称。
  • sql 语句中使用 #{} 字符:它代表占位符,相当于原来 jdbc 部分所学的 ? ,都是用于执行语句时替换实际的数据。具体的数据是由 #{} 里面的内容决定的。
  • #{} 中内容的写法:由于我们保存方法的参数是 一个 User 对象,此处要写 User 对象中的属性名称。它用的是 ognl 表达式。
  • ognl 表达式:它是 apache 提供的一种表达式语言,全称是:Object Graphic Navigation Language 对象图导航语言, 它是按照一定的语法格式来获取数据的。语法格式就是使用 #{对象.对象} 的方式
    #{user.username} 它会先去找 user 对象,然后在 user 对象中找到 username 属性,并调用 getUsername() 方法把值取出来。但是我们在 parameterType 属性上指定了实体类名称,所以可以省略 user, 而直接写 username

测试

    @Test
    public void testsaveUser() {
        User user = new User();
        user.setUsername("张三");
        user.setSex("男");
        user.setAddress("未知");
        user.setBirthday(new Date());

        userDao.saveUser(user);
        System.out.println("user = " + user);
    }

在实现增删改时一定要去控制事务的提交(这里写在了测试方法中 destroy)

修改信息

dao层

int updateUser(User user);

配置文件

    <!--    更新-->
    <update id="updateUser" parameterType="com.example.domain.User">
        update user set username=#{username}, address=#{address}, sex=#{sex}, birthday=#{birthday} where id=#{id};
    </update>

测试

    @Test
    public void testupdateUser(){
        User user = new User();
        user.setId(48);
        user.setUsername("李四");
        user.setSex("男");
        userDao.updateUser(user);
    }

删除信息

dao

int deleteUser(Integer userId);

配置文件

    <!--    删除-->
    <delete id="deleteUser" parameterType="int">
        delete from user where id=#{id};
    </delete>

测试

    @Test
    public void testdeleteUser() {
        userDao.deleteUser(45);
    }

模糊查询

dao

List<User> findByName(String username);

配置文件

    <!--    根据姓名模糊查询-->
    <select id="findByName" parameterType="string" resultType="com.example.domain.User">
        select * from user where username like #{id};
    </select>

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

#{id} 参数就一个,写什么都无所谓了,

测试

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

使用聚合函数

总记录数

dao

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

配置文件

    <!--    获取用户数量-->
    <select id="findTotal" resultType="int">
        select count(id) from user;
    </select>

测试

    @Test
    public void testfindTotal() {
        int total = userDao.findTotal();
        System.out.println("total = " + total);
    }

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 定义输出结果的类型。

parameterType 配置参数

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

基本类型和 String 可以直接写类型名称,也可以使用包名.类名的方式,例如: java.lang.String
实体类类型,目前只能使用全限定类名。
究其原因,是 mybaits 在加载时已经把常用的数据类型注册了别名,从而我们在使用时可以不写包名,
而我们的是实体类并没有注册别名,所以必须写全限定类名

具体的别名映射关系可以在官网查看

传递 pojo 包装对象

开发中通过 pojo 传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。
Pojo 类中包含 pojo
需求:根据用户名查询用户信息,查询条件放到 QueryVouser 属性中。

编写 QueryVo

import java.io.Serializable;

public class QueryVo implements Serializable {
    public User getUser() {
        return user;
    }

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

    private User user;
}

dao 层

    List<User> findUserByQueryVo(QueryVo queryVo);

配置文件

<!--    根据 queryvo 查询-->
    <select id="findUserByQueryVo" parameterType="com.example.domain.QueryVo" resultType="com.example.domain.User">
        select * from user where sex = #{user.sex};
    </select>

测试

    @Test
    public void testfindUserByQueryVo() {
        QueryVo queryVo = new QueryVo();
        User user = new User();
        user.setSex("女");
        queryVo.setUser(user);

        List<User> users = userDao.findUserByQueryVo(queryVo);

        for (User u : users) {
            System.out.println("u = " + u);
        }
    }

resultType 配置结果类型

resultType 属性可以指定结果集的类型,它支持基本类型和实体类类型。
需要注意的是,它和 parameterType 一样,如果注册过类型别名的,可以直接使用别名。没有注册过的必须使用全限定类名。例如:我们的实体类此时必须是全限定类名, 同时,当是实体类名称是,还有一个要求,实体类中的属性名称必须和查询语句中的列名保持一致,否则无法实现封装。

基本类型

dao

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

配置文件

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

实体类类型

dao

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

xml

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

resultMap 结果类型

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

定义 resultMap

<!-- 建立 User 实体和数据库表的对应关系
type 属性:指定实体类的全限定类名
id 属性:给定一个唯一标识,是给查询 select 标签引用用的。
-->
<resultMap type="com.example.domain.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 属性:用于指定实体类属性名称

配置文件

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

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://localhost:3306/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://localhost:3306/eesy
jdbc.username=root
jdbc.password=1234

properties 标签配置

<!-- 配置连接数据库的信息
resource 属性:用于指定 properties 配置文件的位置,要求配置文件必须在类路径下
resource="jdbcConfig.properties"
url 属性:
URL: Uniform Resource Locator 统一资源定位符
http://localhost:8080/mystroe/CategoryServlet URL
协议 主机 端口 URI
URI:Uniform Resource Identifier 统一资源标识符
/mystroe/CategoryServlet
它是可以在 web 应用中唯一定位一个资源的路径
-->
<properties url=
file:///D:/IdeaProjects/mybatisCRUD/src/main/resources/jdbcConfig.prop
erties">
</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(类型别名)

<typeAliases>
    <!-- 单个别名定义 不区分大小写 -->
    <typeAlias alias="user" type="com.example.domain.User"/>
    <!-- 批量别名定义,当指定之后,该包下的实体类都会注册别名,并且类名就是别名,不再区分大小写 -->
    <package name="com.example.domain"/>
    <package name="其它包"/>
</typeAliases>

mappers(映射器)

使用相对于类路径的资源
如:<mapper resource="com/example/dao/IUserDao.xml" />

使用 mapper 接口类路径
如:<mapper class="com.example.dao.UserDao"/>
注意:此种方法要求接口名称和映射文件名称相同,且放在同一个目录中。

用于指定dao接口所在的包,当指定了之后就不需要再写 mapper 以及 resource 或者 class 了
如:<package name="com.example.dao"/>
注意:此种方法要求接口名称和映射文件名称相同,且放在同一个目录中。 

Mybatis 连接池与事务

Mybatis 连接池的分类

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

  1. UNPOOLED: 不使用连接池的数据源
  2. POOLED: 使用连接池的数据源
  3. JNDI: 使用 JNDI 实现的数据源

MyBatis 内部分别定义了实现了 java.sql.DataSource 接口的 UnpooledDataSourcePooledDataSource 类来表示 UNPOOLEDPOOLED 类型的数据源。

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

Mybatis 中数据源的配置

            <dataSource type="POOLED">
<!--            配置数据库的基本信息-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test1?serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>

MyBatis 在初始化时,根据 <dataSource> 的 type 属性来创建相应类型的的数据源 `DataSource`, 即:
type="POOLED": MyBatis 会创建 `PooledDataSource` 实例
type="UNPOOLED": MyBatis 会创建 `UnpooledDataSource` 实例
type="JNDI": MyBatis 会从 JNDI 服务上查找 `DataSource` 实例,然后返回使用

Mybatis 中 DataSource 的存取

MyBatis 是通过工厂模式来创建数据源 DataSource 对象的, MyBatis 定义了抽象的工厂 接口: org.apache.ibatis.datasource.DataSourceFactory , 通过其 getDataSource() 方法返回数据源 DataSource

Mybatis 的事务控制

默认手动提交

session.commit();

开启自动提交, 在生成 SqlSession 对象 时设置自动提交

//        3. 使用工厂生产 SqlSession 对象
        sqlSession = sqlSessionFactory.openSession();

public interface SqlSessionFactory

  SqlSession openSession(boolean autoCommit);

Mybatis 的动态 SQL 语句

动态 SQL 之 <if> 标签

dao

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

配置文件

<!--    if 标签-->
    <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 的作用

测试

    @Test
    public void testfindByUser(){
        User user = new User();
        user.setUsername("%王%");
        List<User> userList = userDao.findByUser(user);

        for (User u : userList) {
            System.out.println("u = " + u);
        }
    }

动态 SQL 之 <where> 标签

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

配置文件

<!--    where 标签 -->
    <select id="findByUser" resultType="com.example.domain.User" parameterType="com.example.domain.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>

动态 SQL 之 <foreach> 标签

传入多个 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)
这样我们在进行范围查询时,就要将一个集合中的值,作为参数动态添加进来。

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

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

public class QueryVo implements Serializable {
    private List<Integer> ids;

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

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

配置文件

<!--    查询所有用户在 id 的集合之中 -->
    <select id="findInIds" resultType="com.example.domain.User" parameterType="com.example.domain.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`: 代表分隔符

测试

    @Test
    public void testfindbyids(){
        QueryVo queryVo = new QueryVo();
        LinkedList<Integer> linkedList = new LinkedList<>();
        linkedList.add(46);
        linkedList.add(47);
        linkedList.add(48);
        linkedList.add(49);

        queryVo.setIds(linkedList);

        List<User> users = userDao.findInIds(queryVo);

        for (User user : users) {
            System.out.println("user = " + user);

        }
    }

Mybatis 中简化编写的 SQL 片段

Sql 中可将重复的 sql 提取出来,使用时用 include 引用即可,最终达到 sql 重用的目的。

定义代码片段(配置文件)

com/example/dao/UserDao.xml

<!--    抽取重复的语句代码片段 -->
    <sql id="defaultSql">
        select * from user
    </sql>

引用代码片段

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

<!--    根据 id 查询 -->
    <select id="findById" resultType="user" parameterType="int">
        <include refid="defaultSql"></include>
        where id = #{uid}
    </select>

Mybatis 多表查询之一对多

以用户和账户为例, 一个账号只能有一个用户, 而一个用户可以有多个账户

方式一

定义账户信息的实体类

com.example.domain;

import java.io.Serializable;

public class Account implements Serializable {
    private Integer id;
    private Integer uid;
    private Double money;
}

编写 sql 语句进行测试

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

定义 AccountUser 类

定义 AccountUser 类中要包含账户信息同时还要包含用户信
息,所以我们要在定义 AccountUser类时可以继承 User 类。 

因为是继承的 User , 拥有了 User 的所有属性, 只需要添加其它需要的字段即可

也可以继承 Account , 然后添加 User 的其它字段, 灵活选择即可

com.example.domain

import java.io.Serializable;

public class AccountUser extends User implements Serializable {
    private Integer id;
    private Integer uid;
    private Double money;
}

定义账户的持久层 Dao 接口

com.example.dao

import com.example.domain.AccountUser;

import java.util.List;

public interface AccountDao {
    List<AccountUser> findAll();
}

定义 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.example.dao.AccountDao">
    <select id="findAll" resultType="AccountUser">
        select a.* ,u.username, u.address from account a, user u where a.uid =u.id;
    </select>
</mapper>

测试

public class AccountTest {
    InputStream resourceAsStream;
    SqlSession sqlSession;
    AccountDao accountDao;

    @Before
    public void init() throws Exception {
        //        1. 读取配置文件
        resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");

//        2. 创建 SqlSessionFactory 工厂
        SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = factoryBuilder.build(resourceAsStream);

//        3. 使用工厂生产 SqlSession 对象
        sqlSession = sqlSessionFactory.openSession();

//        4. 使用 SqlSession 创建 AccountDao 的代理对象
        accountDao = sqlSession.getMapper(AccountDao.class);
    }

    @After
    public void destroy() throws Exception {
        // 事务
        sqlSession.commit();

        //        6. 释放资源
        sqlSession.close();
        resourceAsStream.close();
    }

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

定义专门的 pojo 类作为输出类型,其中定义了 sql 查询结果集所有的字段。

方式二

使用 resultMap,定义专门的resultMap用于映射一对一查询结果。
可以在 Account 类中加入一个 User 类的对象来代表这个账户是哪个用户的。

修改 Account 类

public class Account implements Serializable {
    private Integer id;
    private Integer uid;
    private Double money;
    private User user;
}

修改 AccountDao 接口中的方法

List<Account> findAll(); 

注意:第二种方式,将返回值改 为了Account 类型。
因为 Account类中包含了一个 User类的对象,它可以封装账户所对应的用户信息。

重新定义 AccountDao.xml 文件

    <!-- 建立对应关系 -->
    <resultMap type="com.example.domain.Account" id="accountMap">
        <id column="aid" property="id"/>
        <result column="uid" property="uid"/>
        <result column="money" property="money"/>
        <!-- 它是用于指定从表方的引用实体属性的 -->
        <association property="user" column="uid" javaType="user">
            <id column="id" property="id"/>
            <result column="username" property="username"/>
            <result column="sex" property="sex"/>
            <result column="birthday" property="birthday"/>
            <result column="address" property="address"/>
        </association>
    </resultMap>

    <select id="findAll" resultMap="accountMap">
        select u.*,a.id as aid,a.uid,a.money from account a,user u where a.uid =u.id;
    </select>

测试

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

一对多查询

查询用户的所有账户

编写 SQL 语句

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

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

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

    List<User> findAll();

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

    <resultMap type="user" id="userMap">
        <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是用于建立一对多中集合属性的对应关系
        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中的对象类型。此处可以使用别名,也可以使用全限定名。

测试

    @Test
    public void testfindAll() throws IOException {
//        5. 使用代理对象执行方法
        List<User> all = userDao.findAll();

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

Mybatis 多表查询之多对多

双向一对多

实现 Role 到 User 多对多

业务及 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 
INNER JOIN  
   USER_ROLE ur 
ON ( r.id = ur.rid) 
INNER JOIN 
   USER u 
ON (ur.uid = u.id); 

编写角色实体类

public class Role implements Serializable {
    private Integer roleId;
    private String roleName;
    private String roleDesc;

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

编写 Role 持久层接口

public interface RoleDao {
    List<Role> findAll();
}

编写映射文件

    <!--定义 role 表的 ResultMap -->
    <resultMap id="roleMap" type="role">
        <id property="roleId" column="rid"/>
        <result property="roleName" column="role_name"/>
        <result property="roleDesc" column="role_desc"/>
        <collection property="users" ofType="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>
    </resultMap>

    <!--查询所有-->
    <select id="findAll" resultMap="roleMap">
        select u.*, r.id as rid, r.role_name, r.role_desc
        from role r
                 left outer join user_role ur on r.id = ur.rid
                 left outer join user u on u.id = ur.uid
    </select>

编写测试类

    @Test
    public void testFindAll() {
        List<Role> roles = roleDao.findAll();
        for (Role role : roles) {
            System.out.println("role = " + role);
            System.out.println(role.getUsers());
        }
    }

实现 User 到 Role 的多对多

过程同上, 在 User 实体类中添加 Role 属性

sql 语句

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

配置文件

    <resultMap id="rolesMap" 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="roles" ofType="role">
            <id column="rid" property="roleId"/>
            <result column="role_Name" property="roleName"/>
            <result column="role_Desc" property="roleDesc"/>
        </collection>
    </resultMap>

    <select id="findAllRoles" resultMap="rolesMap">
        select u.*, r.id rid, r.ROLE_DESC, r.ROLE_NAME
        from user u
        left outer join user_role ur on u.id = ur.uid
        left outer join role r on r.id = ur.rid
    </select>

Mybatis 延迟加载策略

使用 assocation 实现延迟加载

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

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

需要注意位置顺序, 也就是说 settings 这个标签必须放在 properties 后面, typeAliases 前面

元素类型为 "configuration" 的内容必须匹配 "(properties?,settings?, typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)"

配置文件

com/example/dao/AccountDao.xml

    <!-- 建立对应关系 -->
    <resultMap type="com.example.domain.Account" id="accountMap">
        <id column="id" property="id"/>
        <result column="uid" property="uid"/>
        <result column="money" property="money"/>
        <!-- 它是用于指定从表方的引用实体属性的 -->
        <association property="user" column="uid" 
                     javaType="user" 
                     select="com.example.dao.UserDao.findById">
        </association>
    </resultMap>

    <select id="findAll" resultMap="accountMap">
        select * from account
    </select>

select: 填写要调用的 select 映射的 id  
column : 填写要传递给 select 映射的参数 

com/example/dao/UserDao.xml

    <!-- 根据id查询信息-->
    <select id="findById" parameterType="int" resultType="com.example.domain.User">
        select * from user where id=#{id};
    </select>

测试

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

仅调用 `accountDao.findAll()` 方法只会查询 `select * from account`

使用 Collection 实现延迟加载

<collection> 结点中也有 select 属性, column 属性

用法同上

    <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.example.dao.AccountDao.findByUid" 
        column="id"> 
        </collection> 
    </resultMap> 

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

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

Mybatis 缓存

一级缓存

一级缓存:
它指的是 Mybatis 中 SqlSession 对象的缓存。
当我们执行查询之后,查询的结果会同时存入到 SqlSession 为我们提供一块区域中。
该区域的结构是一个 Map 。当我们再次查询同样的数据,mybatis 会先去 sqlsession
查询是否有,有的话直接拿出来用。
SqlSession 对象消失时,mybatis 的一级缓存也就消失了。

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

二级缓存

二级缓存:
它指的是 Mybatis 中 SqlSessionFactory 对象的缓存。由同一个 SqlSessionFactory 对象创建的 SqlSession 共享其缓存。
二级缓存的使用步骤:

  • 第一步:让 Mybatis 框架支持二级缓存(在 SqlMapConfig.xml 中配置)
  • 第二步:让当前的映射文件支持二级缓存(在 UserDao.xml 中配置)
  • 第三步:让当前的操作支持二级缓存(在 select 标签中配置)

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

存储的是数据而不是对象

二级缓存的开启与关闭

在 SqlMapConfig.xml 文件开启二级缓存

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

因为 cacheEnabled 的取值默认就为 true,所以这一步可以省略不配置。为 true 代表开启二级缓存;为 false 代表不开启二级缓存。 

配置相关的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.example.dao.UserDao"> 
 <!-- 开启二级缓存的支持 --> 
 <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,禁用二级缓存。 

测试二级缓存的时候注意清除一级缓存

Mybatis 注解开发

注意全局配置, 不能同时存在注解和xml配置, 即使xml中声明了使用注解

mybatis 的常用注解说明

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

使用 Mybatis 注解实现基本 CRUD

public interface UserDao {

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

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

    /**
     * 保存操作
     *
     * @param user
     * @return
     */
    @Insert("insert into user(username, sex, birthday, address) values(#{username},#{sex},#{birthday},#{address})")
    @SelectKey(keyColumn = "id", keyProperty = "id", resultType = Integer.class, before = false, statement = {"select last_insert_id()"})
    int saveUser(User user);

    /**
     * 更新操作
     *
     * @param user
     * @return
     */
    @Update("update user set username=#{username}, address=#{address}, sex=#{sex}, birthday=#{birthday} where id=#{id}")
    int updateUser(User user);

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

    /**
     * 查询使用聚合函数
     *
     * @return
     */
    @Select("select count(*) from user ")
    int findTotal();

    /**
     * 模糊查询
     *
     * @param name
     * @return
     */
    @Select("select * from user where username like #{username} ")
    List<User> findByName(String name);
}


通过注解方式,我们就不需要再去编写 `UserDao.xml` 映射文件了。 

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

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

复杂关系映射的注解说明

`@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=""))`

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

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

实体类还是使用之前的, 在 Account 实体类中添加一个 User 属性

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

public interface AccountDao { 
 
    /** 
    * 查询所有账户,采用延迟加载的方式查询账户的所属用户 
    * @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(column="uid", property="user", 
            one=@One(select="com.example.dao.UserDao.findById", 
            fetchType=FetchType.LAZY)) 
    })
    List<Account> findAll(); 
}

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

public interface UserDao { 
    /** 
    * 查询所有用户 
    * @return 
    */ 
    @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(); 

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

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

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

User 实体类加入 List<Account>

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

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="sex",property="userSex"), 
            @Result(column="address",property="userAddress"), 
            @Result(column="birthday",property="userBirthday"), 
            @Result(column="id",property="accounts", 
                many=@Many( 
                select="com.example.dao.AccountDao.findByUid", 
                fetchType=FetchType.LAZY 
            )) 
    })
    List<User> findAll(); 
} 
 
@Many: 
相当于 `<collection>` 的配置 
`select` 属性: 代表将要执行的sql语句 
`fetchType` 属性: 代表加载方式,一般如果要延迟加载都设置为 `LAZY` 的值 

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

public interface IAccountDao { 
    /** 
    * 根据用户 id 查询用户下的所有账户 
    * @param userId 
    * @return 
    */ 
    @Select("select * from account where uid = #{uid}") 
    List<Account> findByUid(Integer userId); 
} 

mybatis 基于注解的二级缓存

在 SqlMapConfig 中开启二级缓存支持

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

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

@CacheNamespace(blocking=true)//mybatis 基于注解方式实现配置二级缓存 
public interface UserDao {} 
posted @ 2020-01-01 00:06  寒菱  阅读(387)  评论(0编辑  收藏  举报