MyBatis 进阶实战(四): 连接池、动态 SQL、多表关联(一对多 / 多对一 / 多对多)

前三篇学习了 MyBatis 代理 Dao 开发、CRUD、缓存、延迟加载等内容。本篇的主要内容包括:

  1. MyBatis 内置连接池
  2. 动态 SQL(if /where/foreach /sql 标签)
  3. 多表关联:一对多、多对一(一对一)、多对多查询所有代码完整可运行、无报错、严格遵循 mybatis-3-mapper.dtd 规范

一、MyBatis 的连接池

1. 回顾连接池技术

什么是连接池?

  • 存储数据库连接(Connection)的容器
  • 程序启动时初始化一批连接,使用完毕归还,而不是销毁

解决了什么问题?

  • 没有连接池:每次执行 SQL 都创建 Connection → 耗时、耗资源、性能差
  • 有连接池:连接复用,大幅提升程序执行效率

2. MyBatis 连接池的分类

MyBatis 自带连接池,通过主配置文件中 <dataSource> 标签的 type 属性控制:

三种取值:

  1. POOLED
    • 使用 MyBatis 自带连接池
    • 企业开发最常用
  2. UNPOOLED
    • 不使用连接池
    • 每次都创建 / 关闭连接
  3. JNDI
    • 使用服务器(Tomcat)提供的连接池
    • 多用于 Web 项目

主配置文件示例:

XML 复制代码
<dataSource type="POOLED">
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql:///mybatis_db"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</dataSource>

二、动态 SQL

动态 SQL:根据参数不同,自动拼接不同 SQL,是 MyBatis 核心亮点。

1. 动态 SQL 之 if 标签

需求

根据用户名、性别条件组合查询用户。

UserMapper 接口

java 复制代码
package cn.tx.mapper;
import java.util.List;
import cn.tx.domain.User;

/**
 * 用户Mapper
 */
public interface UserMapper {
    // 多条件查询
    public List<User> findByWhere(User user);
}

UserMapper.xml

XML 复制代码
<select id="findByWhere" parameterType="cn.tx.domain.User" resultType="cn.tx.domain.User">
    select * from user where 1 = 1
    <if test="username != null and username != ''">
        and username like #{username}
    </if>
    <if test="sex != null and sex != ''">
        and sex = #{sex}
    </if>
</select>

测试方法

java 复制代码
@Test
public void testFindByWhere() throws IOException {
    InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession session = factory.openSession();

    User user = new User();
    user.setUsername("%熊%");
    user.setSex("女");

    UserMapper mapper = session.getMapper(UserMapper.class);
    List<User> list = mapper.findByWhere(user);
    for (User user1 : list) {
        System.out.println(user1);
    }

    session.close();
    inputStream.close();
}

2. 动态 SQL 之 where 标签

作用

自动去掉多余的 and / or不需要写 where 1=1

UserMapper.xml

XML 复制代码
<select id="findByWhere" parameterType="cn.tx.domain.User" resultType="cn.tx.domain.User">
    select * from user
    <where>
        <if test="username != null and username != ''">
            and username like #{username}
        </if>
        <if test="sex != null and sex != ''">
            and sex = #{sex}
        </if>
    </where>
</select>

3. 动态 SQL 之 foreach 标签

作用

循环遍历集合 / 数组,多用于批量查询、批量删除、批量插入

实体类添加 ids

java 复制代码
public class User implements Serializable{
    // 其他字段...
    private List<Integer> ids;

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

需求一:OR 查询

SQL:

sql 复制代码
select * from user where id = 1 or id = 2 or id =3

XML:

XML 复制代码
<select id="findByIds" parameterType="cn.tx.domain.User" resultType="cn.tx.domain.User">
    select * from user
    <where>
        <foreach collection="ids" open="id = " separator="or id = " item="i">
            #{i}
        </foreach>
    </where>
</select>

需求二:IN 查询(最常用)

SQL:

sql 复制代码
select * from user where id in (1,2,3)

XML:

XML 复制代码
<select id="findByIds" parameterType="cn.tx.domain.User" resultType="cn.tx.domain.User">
    select * from user
    <where>
        <foreach collection="ids" open="id in (" separator="," close=")" item="i">
            #{i}
        </foreach>
    </where>
</select>

测试方法

java 复制代码
@Test
public void testFindByIds() throws IOException {
    InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession session = factory.openSession();

    User user = new User();
    List<Integer> ids = new ArrayList<>();
    ids.add(1);
    ids.add(2);
    ids.add(3);
    user.setIds(ids);

    UserMapper mapper = session.getMapper(UserMapper.class);
    List<User> list = mapper.findByIds(user);
    for (User user1 : list) {
        System.out.println(user1);
    }

    session.close();
    inputStream.close();
}

4. 提取公共 SQL 片段(sql 标签)

作用

把重复 SQL 提取,通过 <include> 引用,便于维护。

完整 UserMapper.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.tx.mapper.UserMapper">

    <!-- 公共SQL -->
    <sql id="findAllSql">
        select * from user
    </sql>

    <!-- 查询所有 -->
    <select id="findAll" resultType="cn.tx.domain.User">
        <include refid="findAllSql" />
    </select>

    <!-- 条件查询 -->
    <select id="findByWhere" parameterType="cn.tx.domain.User" resultType="cn.tx.domain.User">
        <include refid="findAllSql" />
        <where>
            <if test="username != null and username != ''">
                and username like #{username}
            </if>
            <if test="sex != null and sex != ''">
                and sex = #{sex}
            </if>
        </where>
    </select>

    <!-- IN查询 -->
    <select id="findByIds" parameterType="cn.tx.domain.User" resultType="cn.tx.domain.User">
        <include refid="findAllSql" />
        <where>
            <foreach collection="ids" open="id in (" separator="," close=")" item="i">
                #{i}
            </foreach>
        </where>
    </select>

</mapper>

三、一对多 & 多对一 查询

1. 多表关系

  • 一对一
  • 一对多(1 个用户有多个账户)
  • 多对一(多个账户属于 1 个用户)
  • 多对多

2. 建表语句

user 表

sql 复制代码
CREATE TABLE `user` (
  `id` int(11) NOT NULL auto_increment,
  `username` varchar(32) NOT NULL,
  `birthday` datetime default NULL,
  `sex` char(1) default NULL,
  `address` varchar(256) default NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into `user` values
(1,'老王','2018-02-27 17:47:08','男','北京'),
(2,'熊大','2018-03-02 15:09:37','女','上海'),
(3,'熊二','2018-03-04 11:34:34','女','深圳'),
(4,'光头强','2018-03-04 12:04:06','男','广州');

account 表

sql 复制代码
CREATE TABLE `account` (
  `ID` int(11) NOT NULL,
  `UID` int(11) default NULL,
  `MONEY` double default NULL,
  PRIMARY KEY  (`ID`),
  KEY `FK_Reference_8` (`UID`),
  CONSTRAINT `FK_Reference_8` FOREIGN KEY (`UID`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into `account` values
(1,1,1000),
(2,2,1000),
(3,2,2000);

3. 多对一(一对一)查询:查询账户,带出对应用户

Account 实体

java 复制代码
package cn.tx.domain;
import java.io.Serializable;

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

    // 多对一:账户属于一个用户
    private User user;

	// get/set
}

AccountMapper 接口

java 复制代码
package cn.tx.mapper;
import cn.tx.domain.Account;
import java.util.List;

public interface AccountMapper {
    public List<Account> findAll();
}

AccountMapper.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.tx.mapper.AccountMapper">

    <select id="findAll" resultMap="accountMap">
        select a.*,u.username,u.address
        from account a,user u
        where a.uid = u.id
    </select>

    <resultMap id="accountMap" type="cn.tx.domain.Account">
        <result property="id" column="id" />
        <result property="uid" column="uid"/>
        <result property="money" column="money"/>

        <!-- 多对一:association -->
        <association property="user" javaType="cn.tx.domain.User">
            <result property="username" column="username"/>
            <result property="address" column="address"/>
        </association>
    </resultMap>

</mapper>

测试

java 复制代码
@Test
public void testFindAll() throws IOException {
    InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession session = factory.openSession();

    AccountMapper mapper = session.getMapper(AccountMapper.class);
    List<Account> list = mapper.findAll();
    for (Account account : list) {
        System.out.println(account);
    }

    session.close();
    inputStream.close();
}

4. 一对多查询:查询用户,带出所有账户

User 实体添加集合

java 复制代码
public class User implements Serializable{
    // 其他字段...

    // 一对多:一个用户多个账户
    private List<Account> accounts;

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

UserMapper 接口

java 复制代码
// 一对多查询
public List<User> findOneToMany();

UserMapper.xml

XML 复制代码
<select id="findOneToMany" resultMap="userMap">
    select u.*,a.money
    from user u
    left join account a on u.id = a.uid
</select>

<resultMap type="cn.tx.domain.User" id="userMap">
    <result property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="birthday" column="birthday"/>
    <result property="sex" column="sex"/>
    <result property="address" column="address"/>

    <!-- 一对多:collection -->
    <collection property="accounts" ofType="cn.tx.domain.Account">
        <result property="money" column="money"/>
    </collection>
</resultMap>

测试

java 复制代码
@Test
public void testOneToMany() throws IOException {
    InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession session = factory.openSession();

    UserMapper mapper = session.getMapper(UserMapper.class);
    List<User> list = mapper.findOneToMany();
    for (User user : list) {
        System.out.println(user);
    }

    session.close();
    inputStream.close();
}

四、多对多查询

1. 多对多表结构

用户 ↔ 角色(中间表 user_role)

建表语句

sql 复制代码
CREATE TABLE `role` (
  `ID` int(11) NOT NULL,
  `ROLE_NAME` varchar(30) default NULL,
  `ROLE_DESC` varchar(60) default NULL,
  PRIMARY KEY  (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into `role` values
(1,'组长','管理整个组'),
(2,'班主任','管理整个班级'),
(3,'校长','管理整个学校');

CREATE TABLE `user_role` (
  `UID` int(11) NOT NULL,
  `RID` int(11) NOT NULL,
  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` values (1,1),(1,2),(2,2);

2. Role 实体

java 复制代码
package cn.tx.domain;
import java.io.Serializable;
import java.util.List;

public class Role implements Serializable{
    private Integer id;
    private String role_name;
    private String role_desc;

    // 多对多
    private List<User> users;

	// get/set
}

3. RoleMapper 接口

java 复制代码
package cn.tx.dao;
import java.util.List;
import cn.tx.domain.Role;

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

4. RoleMapper.xml

XML 复制代码
<select id="findAll" resultMap="roleMap">
    SELECT r.*,u.username
    FROM USER u,user_role ur,role r
    WHERE u.id = ur.UID AND ur.RID = r.ID
</select>

<resultMap type="cn.tx.domain.Role" id="roleMap">
    <id property="id" column="id"/>
    <result property="role_name" column="role_name"/>
    <result property="role_desc" column="role_desc"/>

    <!-- 多对多:collection -->
    <collection property="users" ofType="cn.tx.domain.User">
        <result property="username" column="username"/>
    </collection>
</resultMap>

5. 测试

java 复制代码
@Test
public void run4() throws Exception {
    InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
    SqlSession session = factory.openSession();

    RoleDao dao = session.getMapper(RoleDao.class);
    List<Role> list = dao.findAll();
    for (Role role : list) {
        System.out.println(role);
    }

    session.close();
    in.close();
}

五、总结

  1. 连接池:POOLED 开启、UNPOOLED 关闭、JNDI 服务器池
  2. 动态 SQL
    • <if>:条件判断
    • <where>:自动去 and
    • <foreach>:循环(in、or)
    • <sql> + <include>:抽取公共 SQL
  3. 多表映射标签
    • 多对一<association> + javaType
    • 一对多<collection> + ofType
    • 多对多<collection> + 中间表
  4. 所有多表查询使用 resultMap,不能使用 resultType
相关推荐
maqr_1102 小时前
如何在 macOS 上为 PHP 8.0 正确集成 XML-RPC 支持
jvm·数据库·python
2301_782659182 小时前
如何在 JavaScript 循环中动态构建 HTML 字符串
jvm·数据库·python
HookJames2 小时前
让 FlyingPress 的 Preload 队列变少,减轻 PHP 和数据库压力
android·数据库·php
2301_816660212 小时前
如何处理MongoDB分片集群的连接池耗尽危机_客户端连接与mongos到shard的连接乘数效应
jvm·数据库·python
梅羽落2 小时前
conda下载python老是404下载失败
开发语言·python·conda
qq_413847402 小时前
SQL如何利用JOIN提升数据质量检查_查找不一致的关联数据
jvm·数据库·python
电商API&Tina2 小时前
【1688API接口】1688 开放平台 API 接入心得
java·开发语言·数据库·python·sql·json
2301_773553622 小时前
CSS如何制作响应式图片集布局_利用object-fit填充空间
jvm·数据库·python
吕源林2 小时前
如何获取SQL字符串左侧字符_利用LEFT函数快速截取
jvm·数据库·python