mybatis第三天

该文档是:mybatis框架学习...

博客连接:https://www.loveuluo.cn

日期:2021-01-04

1. mybatis连接池

1.1 连接池介绍

image-20210104103143492

1.2 Mybatis连接池的分类

mybatis连接池提供了3种方式的配置:
配置的位置:
主配置文件SqlMapConfig.xml中的dataSource标签,type属性就是表示采用何种连接池方式。
type属性的取值:
POOLED(常用): 采用传统的javax.sql.DataSource规范中的连接池,mybatis中有针对规范的实现
UNPOOLED : 采用传统的获取连接的方式,虽然也实现Javax.sql.DataSource接口,但是并没有使用池的思想。(直接获取一个连接)
JNDI: 采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器所能拿到DataSource是不一样。
注意:如果不是web或者maven的war工程,是不能使用的。

        我们课程中使用的是tomcat服务器,采用连接池就是dbcp连接池。

image-20210104105541596

POOLED 原理:

image-20210104105724659

image-20210104110533612

2. Mybatis的事务控制

2.1 JDBC中事务的回顾

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

image-20210104153904876

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

2.2 Mybatis中事务提交方式

Mybatis中事务的提交方式,本质上就是调用JDBC的setAutoCommit()来实现事务控制。 我们运行之前所写的代码:

public class MyBatisTest {
    private SqlSession sqlSession;
    private InputStream in=null;

    //用于返回一个UserDao的代理对象
    public UserDao chuShi() throws IOException {
        //1.读取配置文件
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);
        //3.使用工厂生产SqlSession对象
        sqlSession = factory.openSession();
        //4.使用SqlSession创建Dao接口的代理对象
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        return userDao;
    }
    //用于关闭连接的方法
    public void destroy() throws IOException {
        sqlSession.close();
        in.close();
    }

    //测试保存用户操作
    @Test
    public void Test010() throws IOException {
        //直接调用方法得到代理对象
        UserDao userDao = chuShi();
        User user = new User("曹新千",new Date(),"男","河北");
        userDao.saveUser(user);
        //提交事务
        sqlSession.commit();
        //关闭连接
        destroy();
    }
}

观察在它在控制台输出的结果:

image-20210104154739846

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

2.3 Mybatis自动提交事务的设置

通过上面的研究和分析,现在我们一起思考,为什么CUD过程中必须使用sqlSession.commit()提交事务?主要原因就是在连接池中取出的连接,都会将调用connection.setAutoCommit(false)方法,这样我们就必须使用sqlSession.commit()方法,相当于使用了JDBC中的connection.commit()方法实现事务提交。

明白这一点后,我们现在一起尝试不进行手动提交,一样实现CUD操作。

image-20210104155144434

所对应的DefaultSqlSessionFactory类的源代码:

image-20210104155153633

运行的结果如下:

image-20210104155304602

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

3. 动态sql语句

3.1 <if>标签

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

(1)UserDao接口增加方法

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

(2)UserDao.xml配置文件增加

注意:<if>标签的test属性中写的是对象的属性名,如果是包装类的对象要使用OGNL表达式的写法。

<select id="findByUser" parameterType="com.Luo.domain.User" resultType="com.Luo.domain.User">
    <!--这里加上1=1是为了可以进行拼接sql语句-->
    SELECT * FROM user WHERE 1=1
    <if test="username != null">
          AND username = #{username}
    </if>
    <if test="id != null">
        AND id = #{id}
    </if>
</select>

(3)测试程序

这样的话只要id有值就根据id查,只要name有值就根据name查,两个都有就根据两个一起查

//测试根据id或者名字
@Test
public void Test07() throws IOException {
    UserDao userDao = chuShi();
    //设置User只有名字
    User user1=new User();
    user1.setUsername("老王");
    List<User> users1 = userDao.findByUser(user1);
    //设置User只有id
    User user2=new User();
    user2.setId(48);
    List<User> users2 = userDao.findByUser(user2);
    
    System.out.println(users1);
    System.out.println(users2);
    destroy();
}

3.2 <where>标签

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

(1)UserDao.xml配置文件增加

<select id="findByUser" parameterType="com.Luo.domain.User" resultType="com.Luo.domain.User">
    SELECT * FROM user
    <where>
        <if test="username != null">
            AND username = #{username}
        </if>
        <if test="id != null">
            AND id = #{id}
        </if>
    </where>
</select>

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

(1)QueryVo实体类

public class QueryVo {
    private User user;
    private List<Integer> ids;
    //get set方法
}

(2)UserDao接口增加方法

/**
 * 根据QueryVo中提供的id集合,查询用户信息
 * @param queryVo
 * @return
 */
List<User> findUserInIds(QueryVo queryVo);

(3)UserDao.xml配置文件增加

SQL语句:select 字段 from user where id in (?)
<foreach>标签用于遍历集合,它的属性:
collection:代表要遍历的集合元素,注意编写时不要写#{}
open:代表语句的开始部分
close:代表结束部分
item:代表遍历集合的每个元素,生成的变量名(会放到open和close的中间 每个数字用,隔开,类似于 id in (1,2,3)
sperator:代表分隔符

<!--根据id集合查询用户-->
<select id="findUserInIds" parameterType="com.Luo.domain.QueryVo" resultType="com.Luo.domain.User">
    <!-- select * from user where id in (1,2,3,4,5); -->
    SELECT * FROM USER
    <where>
        <!--如果数组不为空 -->
        <if test="ids != null and ids.size() > 0 ">
            <foreach collection="ids" open="id in ( " close=")" item="id" separator=",">
                <!--这里括号里边的值就是上边的item-->
                #{id}
            </foreach>
        </if>
    </where>
</select>

(4)测试程序

//测试根据Query中的集合来进行查询
@Test
public void Test08() throws IOException {
    UserDao userDao = chuShi();
    //往QueryVo对象里边设置数组集合
    QueryVo vo=new QueryVo();
    List<Integer> ids=new ArrayList<>();
    ids.add(46);
    ids.add(47);
    ids.add(48);
    vo.setIds(ids);
    //根据集合内容查询
    List<User> users = userDao.findUserInIds(vo);
    System.out.println(users);
    destroy();
}

3.4 <include>标签

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

(1)UserDao.xml配置文件增加

<!--了解内容,抽取重复的sql语句-->
<sql id="defaultUser">
    SELECT * FROM user
</sql>

<!--配置查询所有-->
<select id="findAll" resultType="com.Luo.domain.User">
    <include refid="defaultUser"></include>
</select>

<!--根据id集合查询用户-->
<select id="findUserInIds" parameterType="com.Luo.domain.QueryVo" resultType="com.Luo.domain.User">
    <!-- select * from user where id in (1,2,3,4,5); -->
    <include refid="defaultUser"></include>
    <where>
        <!--如果数组不为空 -->
        <if test="ids != null and ids.size() > 0 ">
            <foreach collection="ids" open="id in ( " close=")" item="id" separator=",">
                <!--这里括号里边的值就是上边的item-->
                #{id}
            </foreach>
        </if>
    </where>
</select>

4. Mybatis多表查询

image-20210104163048234

4.1 一对一和一对多查询

4.1.1 分析

image-20210104191717548

4.1.2 数据库表

image-20210104163231283

用户表:

image-20210104190021619

账户表:

image-20210104170538256

4.1.3 准备工作

(1)编写Account实体类:

public class Account implements Serializable {

    private Integer id;
    private Integer uid;
    private Double money;
    //get set方法
}

(2)编写AccountDao接口:

public interface AccountDao {

    /**
     * 查询所有账户
     * @return
     */
    List<Account> findAll();
}

(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.Luo.dao.AccountDao">

    <!--配置查询所有-->
    <select id="findAll" resultType="com.Luo.domain.Account">
        SELECT * FROM account
    </select>

</mapper>

(4)编写测试:

public class AccountTest {
    private SqlSession sqlSession;
    private InputStream in=null;

    //用于返回一个AccountDao的代理对象
    public AccountDao chuShi() throws IOException {
        //1.读取配置文件
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);
        //3.使用工厂生产SqlSession对象
        sqlSession = factory.openSession();
        //4.使用SqlSession创建Dao接口的代理对象
        AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
        return accountDao;
    }
    //用于关闭连接的方法
    public void destroy() throws IOException {
        sqlSession.close();
        in.close();
    }

    /**
     * 测试查询所有
     */
    @Test
    public void Test01() throws IOException {
        AccountDao accountDao = chuShi();
        List<Account> accounts = accountDao.findAll();
        for (Account account : accounts) {
            System.out.println(account);
        }
    }
}

执行结果:

image-20210104170606509

4.1.4 实现一对一 - 方法①(用的不多)

通过写acoount子类的方式完成一对一。

(1)分析SQL语句该怎么做:

执行该语句可以得到想要的结果,根据结果编写一个实体类可以让数据封装进去。

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

执行结果:

image-20210104170625542

(2)编写Account子类AccountUser

从父类那继承了id,uid,money。所以只需要再加两个属性,就能把sql语句得到的结果封装进去。

public class AccountUser extends Account{
    private String username;
    private String address;
    
        @Override
    public String toString() {
        //这里先执行父类的toString方法
        return super.toString()+"        AccountUser{" +
                "username='" + username + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
    
    //getset方法
}

(3)编写AccountDao接口

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

(4)编写AccountDao.xml配置文件

<!--查询所有账户,同时获取账户的所属用户名称以及它的地址信息-->
<select id="findAllAccountUser" resultType="com.Luo.domain.AccountUser">
    SELECT a.*,u.username,u.address FROM user u,account a WHERE u.id=a.uid
</select>

(5)编写测试:

/**
 * 查询所有账户,同时获取账户的所属用户名称以及它的地址信息
 */
@Test
public void Test02() throws IOException {
    AccountDao accountDao = chuShi();
    List<AccountUser> accounts = accountDao.findAllAccountUser();
    for (AccountUser account : accounts) {
        System.out.println(account);
    }
}

执行结果:

image-20210104171305509

4.1.5 实现一对一 - 方法②(常用)

建立实体类关系的方式。

image-20210104173838341

(1)编写Account实体类

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

public class Account implements Serializable {

    private Integer id;
    private Integer uid;
    private Double money;
    private User user;
    //get set方法
}

(2)编写AccountDao接口

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

(3)编写AccountDao.xml配置文件

<!-- 建立对应关系 -->
<resultMap id="accountUserMap" type="com.Luo.domain.Account">
    <result property="uid" column="uid"></result>
    <result property="money" column="money"></result>
    <!--一对一关系的映射:配置user的内容-->
    <!--这里的property代表对应Account类中的user字段-->
    <!--这里的javaType代表要把数据封装到这个类中-->
    <association property="user" javaType="com.Luo.domain.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>

<!--查询所有账户,同时获取账户的所属用户名称以及它的地址信息-->
<!--resultType改为resultMap引用上边定义的-->
<select id="findAll" resultMap="accountUserMap">
    SELECT a.*,u.username,u.address FROM user u,account a WHERE u.id=a.uid
</select>

(4)编写测试:

/**
 * 测试查询所有账户,同时获取账户的所属用户名称以及它的地址信息
 */
@Test
public void Test01() throws IOException {
    AccountDao accountDao = chuShi();
    List<Account> accounts = accountDao.findAll();
    for (Account account : accounts) {
        System.out.println("每一个账户:");
        System.out.println(account);
        System.out.println(account.getUser());
    }
}

执行结果:

image-20210104174548771

4.1.6 实现一对多

给数据库account表增加一个账户,uid和第一个相同:

image-20210104185644034

需求和分析:

image-20210104175056982

(1)分析SQL语句该怎么做:

执行该语句可以得到想要的结果,根据结果编写User类增加一个属性存放Account,这里使用List因为一个人可能有多个账户。

SELECT u.*,a.id as aid,a.uid,a.money FROM user u LEFT JOIN account a ON u.id=a.uid;

执行结果:

image-20210104191137815

(2)编写User类:

public class User implements Serializable {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
    //一对多映射关系:主表实体应该包含从表的集合引用
    //这里用List是因为,一个用户可能有多个账户
    private List<Account> accounts;
    //get set方法
}

(3)编写UserDao接口

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

(4)编写UserDao.xml配置文件

<!--定义User的resultMap-->
<resultMap id="userAccountMap" type="com.Luo.domain.User">
    <id property="id" column="id"></id>
    <result property="username" column="username"></result>
    <result property="birthday" column="birthday"></result>
    <result property="sex" column="sex"></result>
    <result property="address" column="address"></result>
    <!--collection标签配置user对象中accounts集合的映射-->
    <!--ofType代表集合中元素的类型-->
    <collection property="accounts" ofType="com.Luo.domain.Account">
        <id property="id" column="aid"></id><!--这里column对应表的别名aid-->
        <result property="uid" column="uid"></result> 
        <result property="money" column="money"></result>
    </collection>
</resultMap>

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

(5)编写测试:

/**
 * 查询所有用户包含账户信息
 */
@Test
public void Test01() throws IOException {
    UserDao userDao = chuShi();
    List<User> users = userDao.findAll();
    for (User user : users) {
        System.out.println("-------每个用户的信息-------");
        System.out.println(user);
        System.out.println(user.getAccounts());
    }
}

执行结果:

根据结果发现mybatis可以自动识别如果一个用户有多个账户的情况,并且会封装到集合中。

image-20210104191415893

4.2 多对多查询

4.2.1 分析

通过前面的学习,我们使用Mybatis 实现一对多关系的维护。多对多关系其实我们看成是双向的一对多关系。

image-20210104191756388

4.2.2 数据库表

image-20210104193924106

用户表:

image-20210104193947401

角色表:

image-20210104194002726

用户角色中间表:

image-20210104194027300

4.2.3 准备工作

(前情提要)这是User类:

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

(1)编写Role实体类:

public class Role {
    private Integer roleid;
    private String roleName;
    private String roleDesc;
    //get set方法
}

(2)编写RoleDao接口:

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

(3)编写RoleDao.xml配置文件:

<resultMap id="roleMap" type="com.Luo.domain.Role">
    <id property="roleid" column="rid"></id>
    <result property="roleName" column="ROLE_NAME"></result>
    <result property="roleDesc" column="ROLE_DESC"></result>
</resultMap>

<select id="findAll" resultMap="roleMap">
    SELECT * FROM role
</select>

(4)编写测试:

public class RoleTest {
    private SqlSession sqlSession;
    private InputStream in=null;

    //用于返回一个RoleDao的代理对象
    public RoleDao chuShi() throws IOException {
        //1.读取配置文件
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);
        //3.使用工厂生产SqlSession对象
        sqlSession = factory.openSession();
        //4.使用SqlSession创建Dao接口的代理对象
        RoleDao roleDao = sqlSession.getMapper(RoleDao.class);
        return roleDao;
    }
    //用于关闭连接的方法
    public void destroy() throws IOException {
        sqlSession.close();
        in.close();
    }

    /**
     * 查询所有角色
     */
    @Test
    public void Test01() throws IOException {
        RoleDao roleDao = chuShi();
        List<Role> roles = roleDao.findAll();
        for (Role role : roles) {
            System.out.println("-------每个角色的信息-------");
            System.out.println(role);
        }
    }
}

执行结果:

image-20210104194840838

4.2.4 实现查询角色获取角色下的用户信息

查询角色的同时获取到用户的信息。

(1)分析SQL语句该怎么做:

image-20210104195943207

执行该语句可以得到想要的结果,根据结果在Role实体类中可以添加一个User类的集合。

SELECT r.id as rid,r.role_name,r.role_desc,u.* FROM role r
LEFT JOIN user_role ur ON r.id = ur.rid
LEFT JOIN user u ON u.id=ur.uid;

执行结果:

image-20210104200652866

(2)编写Role类:

public class Role {
    private Integer roleid;
    private String roleName;
    private String roleDesc;

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

(3)编写RoleDao接口

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

(4)编写RoleDao.xml配置文件

<resultMap id="roleMap" type="com.Luo.domain.Role">
    <id property="roleid" column="rid"></id>
    <result property="roleName" column="ROLE_NAME"></result>
    <result property="roleDesc" column="ROLE_DESC"></result>
    <collection property="users" ofType="com.Luo.domain.User">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="birthday" column="birthday"></result>
        <result property="sex" column="sex"></result>
        <result property="address" column="address"></result>
    </collection>
</resultMap>

<select id="findAll" resultMap="roleMap">
    SELECT r.id as rid,r.role_name,r.role_desc,u.* FROM role r
     LEFT JOIN user_role ur ON r.id = ur.rid
     LEFT JOIN user u ON u.id=ur.uid;
</select>

(5)编写测试:

/**
 * 查询所有角色
 */
@Test
public void Test01() throws IOException {
    RoleDao roleDao = chuShi();
    List<Role> roles = roleDao.findAll();
    for (Role role : roles) {
        System.out.println("-------每个角色和对应的用户信息-------");
        System.out.println(role);
        System.out.println(role.getUsers());
    }
}

执行结果:

image-20210104201451785

4.2.5 实现查询用户获取用户下的角色信息

(1)分析SQL语句该怎么做:

SELECT u.*,r.id as 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 r.id=ur.rid;

执行结果:

image-20210104201723790

(2)编写User类:

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

    //多对多的关系映射:一个用户可以具备多个角色
    private List<Role> roles;
    //get set方法
}

(3)编写UserDao接口

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

(4)编写UserDao.xml配置文件

<!--定义User的resultMap-->
<resultMap id="userMap" type="com.Luo.domain.User">
    <id property="id" column="id"></id>
    <result property="username" column="username"></result>
    <result property="birthday" column="birthday"></result>
    <result property="sex" column="sex"></result>
    <result property="address" column="address"></result>
    <!--配置角色集合的映射-->
    <collection property="roles" ofType="com.Luo.domain.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 as 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 r.id=ur.rid;
</select>

(5)编写测试:

/**
 * 查询所有用户包含账户信息
 */
@Test
public void Test01() throws IOException {
    UserDao userDao = chuShi();
    List<User> users = userDao.findAll();
    for (User user : users) {
        System.out.println("-------每个用户和对应的角色信息-------");
        System.out.println(user);
        System.out.println(user.getRoles());
    }
}

执行结果:

image-20210104202328242

最后修改:2021 年 01 月 21 日 04 : 36 PM
如果觉得我的文章对你有用,请随意赞赏