Java框架-mybatis连接池、动态sql和多表查询

1. mybatis连接池

  • 通过SqlMapConfig.xml设置dataSource type实现连接池的配置

1.1 dataSource标签type属性值含义

type=”POOLED”: MyBatis 会创建 PooledDataSource 实例

type=”UNPOOLED” : MyBatis 会创建 UnpooledDataSource 实例

type=”JNDI”: MyBatis 会从 JNDI 服务上查找 并获取DataSource 实例

1.2 mybatis连接池及dataSource

1.2.1 mybatis连接池分类及关系

  • Mybatis 的数据源分为三类:

    • UNPOOLED 不使用连接池的数据源
    • POOLED 使用连接池的数据源
    • JNDI 使用 JNDI 实现的数据源
  • Mybaits连接池关系

    mybatis连接池关系

    PooledDataSource通过UnPooledDataSource实例对象,实际上是一种缓存连接池机制

1.2.2 UnPooledDataSource连接池

获取连接方式有两种

  1. 通过用户名和密码获取连接(实际上是将用户名、密码封装到属性集,再调用第二种方法获取)
  2. 通过配置文件获取连接

在这里插入图片描述

1.2.3 PooledDataSource连接池(建议使用)

  • PooledDataSource在创建连接时候,会自动调用UnPooledDataSource中创建连接的方法

1.2.4 连接获取时机

  • 在实例数据库时不会创建连接,而是在真正进行数据库操作的时候再获取连接

2. Mybatis映射文件的动态sql

部分业务逻辑是动态变化的,需要使用动态sql满足需求。Mybatis框架中sql是写在dao接口映射文件中,Mybatis提供多种标签用以使用动态sql

2.1 <if>标签

  • 使用if标签进行条件判断
    • 属性test:用于判断表达式,返回boolean类型。其值为条件表达式

2.1.1 映射文件

<!--
使用if条件语句 test属性用以判断,其属性值为条件判断语句
-->
<select id="findByConditon" parameterType="user" resultType="com.azure.entity.User">
    select * from user where 1=1
    <!--条件判断并拼接sql语句-->
    <if test="id != 0">
         and id=#{id}
    </if>
    <if test="username != null">
         and username=#{username}
    </if>
</select>

2.2 <where>标签

  • 使用where标签拼接where条件
<!--使用where标签拼接where条件-->
<select id="findByConditon" parameterType="user" resultType="com.azure.entity.User">
    select * from user
    <where>
        <!--条件判断并拼接sql语句-->
        <if test="id != 0">
            and id=#{id}
        </if>
        <if test="username != null">
            and username=#{username}
        </if>
    </where>
</select>

2.3 <foreach>标签

  • 使用foreach标签遍历参数

    属性值:

    • collection:遍历的集合(参数列表)
    • open:sql语句最先拼接的部分,遍历开始
    • close:sql语句最后拼接的部分,遍历结束
    • separator:每次遍历时,sql语句拼接时以指定的分割符分割
    • item:存储每次遍历的结果
    • index:当前遍历元素的索引

    标签体:给每个item赋值,可使用#{}赋值

案例需求:根据多个id查询用户返回用户集合

2.3.1 定义查询扩展对象

  • 用于封装多个参数
  • 对原有的QueryVo进行拓展优化
/**
 * 通过继承User类,QueryVO类可以实现User所有属性查询以及扩展属性
 */
public class QueryVo extends User{

    /*
    使用一个List封装多个id值
     */
    private List<Integer> ids;

    public QueryVo(){
    }

    //getter&setter
    public List<Integer> getIds() {
        return ids;
    }
    public void setIds(List<Integer> ids) {
        this.ids = ids;
    }
}

2.3.2 dao层接口

List<User> findByCondition2(QueryVo queryVo);

2.3.3 dao映射文件

<!--使用foreach标签进行多id查询
使用代码格式select * from user where 1=1 and id in (xx,xx,...)
格式:select * from user where 1=1 and id=xx orid=xx,...不能使用foreach标签
-->
    <select id="findByCondition2" resultType="user" parameterType="user">
        select * from user
        <where>
            <if test="ids != null and ids.size() > 0">
                <foreach collection="ids" open="id in (" separator="," close=")" item="id">
                    #{id}
                </foreach>
            </if>
        </where>
    </select>

2.3.4 测试类

@Test
public void find2() {
    //封装条件
    QueryVo queryVo = new QueryVo();
    List<Integer> ids = new ArrayList<Integer>();
    ids.add(47);
    ids.add(48);
    ids.add(49);
    queryVo.setIds(ids);

    //执行方法
    List<User> list = userDao.findByCondition2(queryVo);
    System.out.println(list);
}

2.4 Mybatis简化sql语句

  • sql标签:定义sql片段,可以将公用的sql抽取出来

    • 属性值:id,为sql片段命名
  • include标签:用于引用sql片段

    • 属性值:refid,与引用的sql标签的id对应
  • 如果引用其它 mapper.xml 的 sql 片段,则在引用时需要加上 namespace,如下: <include refid=*"*namespace.sql 片段”/>

<!--抽取公共sql语句-->
<sql id="selectUser">
    select * from user
</sql>
<select id="findByCondition2" resultType="user" parameterType="queryVo">
    <!--引用sql片段-->
    <include refid="selectUser"></include>
    <where>
        <if test="ids != null and ids.size() > 0">
            <foreach collection="ids" open=" id in (" separator="," close=")" item="id">
                #{id}
            </foreach>
        </if>
    </where>
</select>
  • 注意事项:
    • 何时进行抽取?如果相同sql代码出现2次以上,就要将相同代码抽取出来,便于后期维护
    • 强烈建议将sql抽取出来!!!!!

3. Mybatis的多表关联查询

以用户、账户和角色表之间查询为例

准备工作:

  1. 账户表

    DROP TABLE IF EXISTS account;
    
    CREATE TABLE account (
      accountId INT(11) NOT NULL COMMENT '编号',
      UID INT(11) DEFAULT NULL COMMENT '用户编号',
      MONEY DOUBLE DEFAULT NULL COMMENT '金额',
      PRIMARY KEY  (accountId),
      KEY FK_Reference_8 (UID),
      CONSTRAINT FK_Reference_8 FOREIGN KEY (UID) REFERENCES USER (id)
    ) ENGINE=INNODB DEFAULT CHARSET=utf8;
    INSERT  INTO account(accountId,UID,MONEY) VALUES (1,46,1000),(2,45,1000),(3,46,2000);
    
  2. 角色表

    CREATE TABLE role (
      ID int(11) NOT NULL COMMENT '编号',
      ROLE_NAME varchar(30) default NULL COMMENT '角色名称',
      ROLE_DESC varchar(60) default NULL COMMENT '角色描述',
      PRIMARY KEY  (ID)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    insert  into role(ID,ROLE_NAME,ROLE_DESC) values (1,'院长','管理整个学院'),(2,'总裁','管理整个公司'),(3,'校长','管理整个学校');
    
    

3.1 一对一

需求:查询账户表,并把账户对应的用户信息显示出来

3.1.1 需求分析

账户与用户是一对一关系,一个账户只能对应一个用户

实现一对一关系步骤:

  1. 创建项目;

  2. 创建实体类(账户)

    • 账户表属性

    • 封装User数据

  3. 接口编写

  4. 配置映射文件,里面配置1to1的关系,让Mybatis自动封装多表数据

  5. 测试

3.1.2 创建实体类

public class Account  implements Serializable {
    private int accountId;
    private int uid;
    private double money;

    //封装User数据,账户与用户是一对一关系
    private User user;

    public Account() {
    }
    /*省略有参、toString、getter&setter*/
}

3.1.3 创建dao接口

public interface IAccountDao {
    //查询所有账户及对应的用户信息
    List<Account> findAll();
}

3.1.4 配置dao接口映射文件

  • 一对一关系中,返回值类型必须使用resultMap,用来封装多表查询的数据

  • 使用<association>标签表示一对一关系的映射配置。

    属性值有:

    • property:一对一关系的对应对象属性名(本例是指账户对象对应的user对象属性);
    • JavaType:对应对象属性类型
    • column:外键字段
<?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.azure.dao.IAccountDao">
    <!--返回集的类型是Account类型,里面需封装account表数据和User对象-->
    <resultMap id="accountResultMap" type="account">
        <!--先建立account对象属性与account表字段的映射关系-->
        <id property="accountId" column="accountId"></id>
        <result property="uid" column="uid"></result>
        <result property="money" column="money"></result>
        <!--建立user对象与User表字段的映射关系-->
        <!--
        使用association标签标示一对一关系
        - property:一对一关系的对应对象属性名(本例是指账户对象对应的user对象属性);
        - JavaType:对应对象属性类型(本例即User类型)
        - column:外键字段
        -->
        <association property="user" javaType="user" column="uid">
            <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>
        </association>
    </resultMap>
    <!--使用resultMap明确一对一关系。
    使用左外连接确保account表数据能全部显示
    -->
    <select id="findAll" resultMap="accountResultMap">
        select * from account a left join user u on a.uid=u.id
    </select>
</mapper>

3.1.5 测试类

public class AccountDaoTest {
    private InputStream is;
    private SqlSession session;
    private IAccountDao accountDao;

    /**
     * 每次执行Junit前都会执行
     * @throws IOException
     */
    @Before
    public void before() throws IOException {
        is = Resources.getResourceAsStream("SqlMapConfig.xml");
        session = new SqlSessionFactoryBuilder().build(is).openSession();
        accountDao = session.getMapper(IAccountDao.class);
    }

    /**
     * 每次执行Junit后都会执行,提交事务和关闭资源
     * @throws IOException
     */
    @After
    public void close() throws IOException {
        //手动提交事务
        session.commit();
        //关闭资源
        session.close();
        is.close();
    }

    @Test
    public void find() throws IOException{
        List<Account> accounts = accountDao.findAll();
        System.out.println(accounts);
    }
}

3.2 一对多

需求:查询用户,并将用户对应的账户显示

3.2.1 需求分析

  • 用户与账户是一对多的关系,一个用户可以有多个账户
  1. 创建项目;

  2. 创建实体类(用户)

    • 用户表属性

    • 封装Account数据

  3. 接口编写

  4. 配置映射文件,里面配置1ton的关系,让Mybatis自动封装多表数据

  5. 测试

3.2.2 创建实体类

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

    //封装account账户表数据,用一个list封装所有账户
    private List<Account> accounts;

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

    public void setAccounts(List<Account> accounts) {
        this.accounts = accounts;
    }
      /*省略有参、toString、getter&setter*/
}

3.2.3 dao接口

public interface IUserDao {
    /*查询所有用户*/
    List<User> findAll();
}

3.2.4 接口映射文件

  • 需要使用resultMap封装多表查询结果

  • 使用<collection>标签表示一对一关系的映射配置。

    属性值有:

    • property:指定集合在实体类中的属性名;(本例是User类中的List<Account>的属性名accounts)
    • ofType:指定集合元素的java类型
    • column:外键
<!--返回集的类型是User类型,里面需封装user表数据和Account对象-->
<resultMap id="usersMap" type="user">
    <!--先建立user对象属性与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>
    <!--建立user对象与account表字段的映射关系-->
    <!--
   使用<collection>标签表示一对一关系的映射配置。
    属性值有:
    - property:对应的集合名;
    - ofType:指定集合元素的类型(本例指List<Account> 的Account类型)
    -->
    <collection property="accounts" ofType="account">
        <id property="accountId" column="accountId"></id>
        <result property="uid" column="uid"></result>
        <result property="money" column="money"></result>
    </collection>
</resultMap>
<select id="findAll" resultMap="usersMap">
    select * from user u left join account a ON u.id = a.UID
</select>

3.2.5 测试类

public class UserDaoTest {
    private InputStream is;
    private SqlSession session;
    private IUserDao userDao;

    /**
     * 每次执行Junit前都会执行
     * @throws IOException
     */
    @Before
    public void before() throws IOException {
        is = Resources.getResourceAsStream("SqlMapConfig.xml");
        session = new SqlSessionFactoryBuilder().build(is).openSession();
        userDao = session.getMapper(IUserDao.class);
    }

    /**
     * 每次执行Junit后都会执行,提交事务和关闭资源
     * @throws IOException
     */
    @After
    public void close() throws IOException {
        //手动提交事务
        session.commit();
        //关闭资源
        session.close();
        is.close();
    }
    
    @Test
    public void findAll() {
        List<User> list = userDao.findAll();
        System.out.println(list);
    }
}

3.3 多对多

  • 所谓的多对多关系,实际上是有多个一对多关系组合起来,所以处理多对多关系,要转换成一对多关系。

需求:查询所有用户,并显示对应的角色

3.3.1 需求分析

用户与角色是多对多的关系,一个用户有多种角色,一种角色也有多个用户,需要分别描述

3.3.1.1 用户对角色的一对多
  1. 创建项目;
  2. 创建实体类
    • User类(User本身数据,List<Role> roles)
    • Role类(Role本身数据)
  3. 接口编写
  4. 配置映射文件,里面配置1ton的关系,让Mybatis自动封装多表数据
  5. 测试
3.3.1.2 角色对用户一对多
  1. 创建项目;
  2. 创建实体类
    • User类(User本身数据,List<Role> roles)
    • Role类(Role本身数据,List<User> users)
  3. 接口编写
  4. 配置映射文件,里面配置1ton的关系,让Mybatis自动封装多表数据
  5. 测试
3.3.1.3 用户表与角色表分析

两个表之间并无主键连接,无法单纯用两个表去描述表之间的关系,故需要添加用户角色关系表用以描述关系。

DROP TABLE IF EXISTS user_role;

CREATE TABLE user_role (
  UID int(11) NOT NULL COMMENT '用户编号',
  RID int(11) NOT NULL COMMENT '角色编号',
  PRIMARY KEY  (UID,RID),
  KEY FK_Reference_10 (RID),
  CONSTRAINT FK_Reference_10 FOREIGN KEY (RID) REFERENCES role (ID),
  CONSTRAINT FK_Reference_9 FOREIGN KEY (UID) REFERENCES user (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert  into user_role(UID,RID) values (41,1),(45,1),(41,2);

3.3.2 实体类

Role类

public class Role  implements Serializable {
    private int id;
    private String roleName;
    private String roleDesc;    
    //封装User用户表数据,使用List封装
    private List<User2> user2s;
    /*省略构造函数、toString、getter&setter
}

User类

这里新建一个User2,避免与上面的User类冲突

public class User2  implements Serializable {
    private int id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
    //封装role角色表数据,使用List封装
    private List<Role> roles;
    /*省略构造函数、toString、getter&setter*/
}

3.3.3 用户对角色一对多

3.3.3.1 dao层接口
/*
查询所有用户,Role有关
 */
List<User2> findAll2();
3.3.3.2 dao接口映射文件

注意:

外连接不能同时连接三个表,下面的写法是错误的:SELECT u.*,r.* FROM USER u LEFT JOIN user_role ur LEFT JOIN role r on u.id=ur.uid and r.ID=ur.rid

正确写法是,先连接user和user_role表,然后将所得的表再与role表连接:SELECT u.*,r.* FROM USER u LEFT JOIN user_role ur ON u.id=ur.uid LEFT JOIN role r ON r.ID=ur.rid

以下的代码有误,请看3.3.3.4优化后代码!!

<!--返回集的类型是User2类型,里面需封装user2表数据和Role对象-->
<resultMap id="user2sMap" type="user2">
    <!--先建立user2对象属性与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>
    <!--建立user对象与role表字段的映射关系-->
    <collection property="roles" ofType="role">
        <id property="id" column="id"></id>
        <result property="roleName" column="role_name"></result>
        <result property="roleDesc" column="role_desc"></result>
    </collection>
</resultMap>
<select id="findAll2" resultMap="user2sMap">
    SELECT u.*,r.* FROM USER u LEFT JOIN user_role ur ON u.id=ur.uid LEFT JOIN role r ON r.ID=ur.rid
</select>
3.3.3.3 测试类
@Test
public void findAll2() {
    List<User2> list2 = userDao.findAll2();
    System.out.println(list2);
}
3.3.3.4 dao接口映射文件(重要)

虽然运行后并没有报错,但是仔细查看结果会发现,有多个角色的用户仅显示一个角色,其余角色都没有显示。

原因:user表的id值与role表的id值冲突!!!在resultMap中标签属性column是不会自动区分多表,所以上述配置中column重复封装了id值

解决方法:给其中一个表的id值起别名

优化后代码
<!--返回集的类型是User2类型,里面需封装user2表数据和Role对象-->
<resultMap id="user2sMap" type="user2">
    <!--先建立user2对象属性与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>
    <!--建立user对象与role表字段的映射关系-->
    <collection property="roles" ofType="role">
        <id property="id" column="roleId"></id><!--使用别名封装role的id值,由于Role类的id属性名就是id,所以property属性不用改-->
        <result property="roleName" column="role_name"></result>
        <result property="roleDesc" column="role_desc"></result>
    </collection>
</resultMap>
<select id="findAll2" resultMap="user2sMap">
    SELECT u.*,r.id roleId,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><!--sql语句要修改,给role表的id值起别名-->

3.3.4 角色对用户的一对多

3.3.4.1 dao接口
public interface IRoleDao {
    /*查询用户及角色*/
    List<Role> findAll();
}
3.3.4.2 dao接口映射
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namesppace名称空间,用于定义是哪个类的映射文件,这里需要写映射接口的类全名-->
<mapper namespace="com.azure.dao.IRoleDao">

    <resultMap id="rolesMap" type="role">
        <id property="id" column="roleId"></id><!--使用别名封装role的id值,由于Role类的id属性名就是id,所以property属性不用改-->
        <result property="roleName" column="role_name"></result>
        <result property="roleDesc" column="role_desc"></result>
        <collection property="user2s" ofType="user2">
            <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="rolesMap">
        SELECT u.*,r.id roleId,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>
3.3.4.3 测试类
public class RoleDaoTest {
    /*
    查询所有角色及其对应用户信息
     */
    private InputStream is;
    private SqlSession session;
    private IRoleDao roleDao;

    /**
     * 每次执行Junit前都会执行
     * @throws IOException
     */
    @Before
    public void before() throws IOException {
        is = Resources.getResourceAsStream("SqlMapConfig.xml");
        session = new SqlSessionFactoryBuilder().build(is).openSession();
        roleDao = session.getMapper(IRoleDao.class);
    }

    /**
     * 每次执行Junit后都会执行,提交事务和关闭资源
     * @throws IOException
     */
    @After
    public void close() throws IOException {
        //手动提交事务
        session.commit();
        //关闭资源
        session.close();
        is.close();
    }

    @Test
    public void findAll() {
        List<Role> list = roleDao.findAll();
        System.out.println(list);
    }
}

猜你喜欢

转载自blog.csdn.net/KeepStruggling/article/details/83449940