前三篇学习了 MyBatis 代理 Dao 开发、CRUD、缓存、延迟加载等内容。本篇的主要内容包括:
- MyBatis 内置连接池
- 动态 SQL(if /where/foreach /sql 标签)
- 多表关联:一对多、多对一(一对一)、多对多查询所有代码完整可运行、无报错、严格遵循 mybatis-3-mapper.dtd 规范。
一、MyBatis 的连接池
1. 回顾连接池技术
什么是连接池?
- 存储数据库连接(Connection)的容器
- 程序启动时初始化一批连接,使用完毕归还,而不是销毁
解决了什么问题?
- 没有连接池:每次执行 SQL 都创建 Connection → 耗时、耗资源、性能差
- 有连接池:连接复用,大幅提升程序执行效率
2. MyBatis 连接池的分类
MyBatis 自带连接池,通过主配置文件中 <dataSource> 标签的 type 属性控制:
三种取值:
- POOLED
- 使用 MyBatis 自带连接池
- 企业开发最常用
- UNPOOLED
- 不使用连接池
- 每次都创建 / 关闭连接
- 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();
}
五、总结
- 连接池:POOLED 开启、UNPOOLED 关闭、JNDI 服务器池
- 动态 SQL
<if>:条件判断<where>:自动去 and<foreach>:循环(in、or)<sql>+<include>:抽取公共 SQL
- 多表映射标签
- 多对一 :
<association>+javaType - 一对多 :
<collection>+ofType - 多对多 :
<collection>+ 中间表
- 多对一 :
- 所有多表查询使用 resultMap,不能使用 resultType