1 什么是MyBatis?
MyBatis
是一款优秀的持久层框架,它支持自定义SQL
、存储过程以及高级映射。MyBatis
免除了几乎所有的JDBC
代码以及设置参数和获取结果集的工作,使得程序员在开发时只需要关注SQL
语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement
等繁杂的过程,花更多的精力在业务开发中。MyBatis
可以通过简单的XML
或注解来配置和映射原始类型、接口和Java POJO
(Plain Old Java Objects
,普通老式Java
对象)为数据库中的记录。
2 MyBatis全局配置文件基础设置
2.1 配置顺序
2.2 属性(properties)
属性可以在外部进行配置,可以进行动态替换。如:
<!-- resource属性:用于指定properties配置文件的位置,要求配置文件必须在类路径下 -->
<properties resource="jdbcConfig.properties">
<!-- 启用默认值特性 -->
<property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/>
</properties>
默认值特性参考官网例子:
<dataSource type="POOLED">
<!-- ... -->
<property name="username" value="${username:ut_user}"/> <!-- 如果属性 'username' 没有被配置,'username' 属性的值将为 'ut_user' -->
</dataSource>
2.3 设置(settings)
<settings>
<!--
开启二级缓存的支持
cacheEnabled:全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。
-->
<setting name="cacheEnabled" value="true"/>
<!--
延迟加载
lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。
aggressiveLazyLoading:开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载。
-->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
<!--
multipleResultSetsEnabled:是否允许单个语句返回多结果集(需要数据库驱动支持)。
-->
<setting name="multipleResultSetsEnabled" value="true"/>
</settings>
2.4 类型别名(typeAliases)
<typeAliases>
<!-- 单个别名定义 -->
<!-- <typeAlias alias="user" type="com.hc.domain.User"/> -->
<!-- 批量别名定义,扫描包下的类,别名为类名(首字母大写或小写都行) -->
<package name="com.hc.domain"/>
</typeAliases>
2.5 插件(plugins)
分页插件如下:
<plugins>
<!-- com.github.pagehelper 为 PageHelper 类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<!-- 设置数据库类型 Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL 六种数据库-->
<property name="dialect" value="mysql"/>
</plugin>
</plugins>
2.6 环境配置(environments)
<!-- 和Spring整合后environments配置将被废除 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username:root}"/> <!-- 默认值 -->
<property name="password" value="${jdbc.password:1234}"/>
</dataSource>
</environment>
</environments>
2.7 映射器(mappers)
<mappers>
<!--XML配置文件-->
<!-- <mapper resource="com/hc/dao/UserDao.xml"/> -->
<!--注解配置-->
<!--<mapper class="com.hc.dao.UserDao"></mapper>-->
<!-- 注册指定包下的所有mapper接口,此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中 -->
<package name="com.hc.dao"/>
</mappers>
3 基于MyBatis实现单表CRUD(XML)
3.1 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>HelloMybatis</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>9</maven.compiler.source>
<maven.compiler.target>9</maven.compiler.target>
</properties>
<dependencies>
<!--MyBatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<!--数据库依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
<!--日志依赖-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
3.2 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">
<configuration>
<!-- 1.properties属性引入外部配置文件 -->
<!-- resource属性:用于指定properties配置文件的位置,要求配置文件必须在类路径下 -->
<properties resource="jdbcConfig.properties">
<!-- 启用默认值特性 -->
<property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/>
</properties>
<!-- 2.配置延迟加载和缓存 -->
<settings>
<!--
开启二级缓存的支持
cacheEnabled:全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。
-->
<setting name="cacheEnabled" value="true"/>
<!--
延迟加载
lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。
aggressiveLazyLoading:开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载。
-->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
<!--
multipleResultSetsEnabled:是否允许单个语句返回多结果集(需要数据库驱动支持)。
-->
<setting name="multipleResultSetsEnabled" value="true"/>
</settings>
<!-- 3.类型别名配置 -->
<typeAliases>
<!-- 单个别名定义 -->
<!-- <typeAlias alias="user" type="com.hc.domain.User"/> -->
<!-- 批量别名定义,扫描包下的类,别名为类名(首字母大写或小写都行) -->
<package name="com.hc.domain"/>
</typeAliases>
<!-- 4.environments数据库环境配置 -->
<!-- 和Spring整合后environments配置将被废除 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username:root}"/> <!-- 默认值 -->
<property name="password" value="${jdbc.password:1234}"/>
</dataSource>
</environment>
</environments>
<!-- 5.加载映射文件 -->
<mappers>
<!--XML配置文件-->
<!-- <mapper resource="com/hc/dao/UserDao.xml"/> -->
<!--注解配置-->
<!--<mapper class="com.hc.dao.UserDao"></mapper>-->
<!-- 注册指定包下的所有mapper接口,此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中 -->
<package name="com.hc.dao"/>
</mappers>
</configuration>
3.3 User类、UserDao接口
package com.hc.domain;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
// 多对多:一个用户可以拥有多个账户
private List<Account> accounts;
// 多对多:一个用户可以赋予多个角色
private List<Role> roles;
/**
* Mybatis框架会调用这个默认构造方法来构造实例对象,即实体类需要通过Mybatis进行动态反射生成。
* 反射的Class.forName("className").newInstance();需要对应的类提供一个无参构造函数。
*/
public User() {
}
public User(Integer id, String username, Date birthday, String sex, String address) {
this.id = id;
this.username = username;
this.birthday = birthday;
this.sex = sex;
this.address = address;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public List<Account> getAccounts() {
return accounts;
}
public void setAccounts(List<Account> accounts) {
this.accounts = accounts;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
", accounts=" + accounts +
", roles=" + roles +
'}';
}
}
package com.hc.dao;
import com.hc.domain.QueryVo;
import com.hc.domain.User;
import java.util.List;
public interface UserDao {
User findOneById(Integer id);
List<User> findAllByName(String username);
List<User> findAllByCondition(User user);
List<User> findAllByQueryVo(QueryVo queryVo);
List<User> findAllAndAccount();
List<User> findAllAndRole();
List<User> findAll();
int findTotal();
void insertOne(User user);
void updateOne(User user);
void deleteOne(Integer id);
}
3.4 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.hc.dao.UserDao">
<!-- 缓存配置 -->
<cache/>
<!-- 可被其它语句引用的可重用语句块 -->
<sql id="defaultSql">
select *
from user
</sql>
<!-- 描述如何从数据库结果集中加载对象 -->
<resultMap id="userMap" type="User">
<id column="id" property="id"></id>
<result column="username" property="username"></result>
<result column="address" property="address"></result>
<result column="sex" property="sex"></result>
<result column="birthday" property="birthday"></result>
<!--
collection是用于建立一对多中集合属性的对应关系,ofType用于指定集合元素的数据类型
此处采用延迟加载,等需要账户信息时再查询。
-->
<collection column="id" property="accounts" ofType="Account" select="com.hc.dao.AccountDao.findOneById">
</collection>
</resultMap>
<!--多表查询,一个用户的所有角色信息-->
<resultMap id="userRoleMap" type="User">
<id column="id" property="id"></id>
<result column="username" property="username"></result>
<result column="address" property="address"></result>
<result column="sex" property="sex"></result>
<result column="birthday" property="birthday"></result>
<!-- collection是用于建立一对多中集合属性的对应关系,ofType用于指定集合元素的数据类型-->
<collection property="roles" ofType="Role">
<id column="rid" property="roleId"></id>
<result column="role_name" property="roleName"></result>
<result column="role_desc" property="roleDesc"></result>
</collection>
</resultMap>
<!--
根据id查询用户,useCache将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来。
-->
<select id="findOneById" parameterType="java.lang.Integer" resultType="com.hc.domain.User" useCache="true">
select *
from user
where id = #{id};
</select>
<!-- 根据用户名模糊查询 -->
<select id="findAllByName" parameterType="java.lang.String" resultType="com.hc.domain.User">
<!-- select * from user where username like #{name}; --> <!-- 使用#{},原理是使用占位符,PrepareStatement语句 -->
select * from user where username like '%${value}%'; <!-- 使用${},原理是使用字符串拼接,当然${value}的写法固定 -->
</select>
<!-- 根据条件查询,采用if标签判断User类中的存在不存在 -->
<!-- where标签替代where 1 = 1语句的功能 -->
<select id="findAllByCondition" resultType="User" parameterType="User">
<include refid="defaultSql"></include>
<where>
<if test="username != null and username != ''">
and username like #{username}
</if>
<if test="address != null">
and address like #{address}
</if>
</where>
</select>
<!-- 根据QueryVo包装类进行查询 -->
<select id="findAllByQueryVo" parameterType="QueryVo" resultType="User">
<include refid="defaultSql"></include>
<where>
<if test="ids != null and ids.size() > 0">
<foreach collection="ids" open="id in (" close=")" item="id" separator=",">
<!--
item="id",此id与#{id}名称需保持一致
-->
#{id}
</foreach>
</if>
</where>
</select>
<select id="findAllAndAccount" resultMap="userMap">
select * from user;
</select>
<!-- 查询所有用户及其角色信息 -->
<select id="findAllAndRole" resultMap="userRoleMap">
select u.*, r.*
from user u
left outer join user_role ur on ur.uid = u.id
left outer join role r on r.id = ur.rid
</select>
<!-- 查询所有 -->
<select id="findAll" resultType="user">
select *
from user;
</select>
<!-- 查询总用户数 -->
<select id="findTotal" resultType="int">
select count(1)
from user;
</select>
<!-- 插入新用户 -->
<insert id="insertOne" parameterType="com.hc.domain.User">
<!--
配置保存时获取插入的id,id是数据库自动增长的。
keyProperty:Java表中字段;keyColumn:数据库中表中字段;resultType:返回类型;order:执行顺序。
-->
<selectKey keyProperty="id" keyColumn="id" resultType="Integer" order="AFTER">
select last_insert_id();
</selectKey>
insert into user(username, birthday, sex, address)
values (#{username}, #{birthday}, #{sex}, #{address});
</insert>
<!-- 更新用户 -->
<update id="updateOne" parameterType="com.hc.domain.User">
update user
set username = #{username},
birthday = #{birthday},
sex=#{sex},
address=#{address}
where id = #{id};
</update>
<!--
删除用户,如果仅有一个参数,#{id}中的id名字可以任意指定
-->
<delete id="deleteOne" parameterType="java.lang.Integer">
delete
from user
where id = #{id};
</delete>
</mapper>
3.5 测试
import com.hc.dao.UserDao;
import com.hc.domain.QueryVo;
import com.hc.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 org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class testMybatis {
private InputStream in;
private SqlSessionFactoryBuilder builder;
private SqlSessionFactory factory;
private SqlSession session;
private UserDao userDao;
@Before
public void init() throws Exception {
// 1.从 XML 中构建 SqlSessionFactory
in = Resources.getResourceAsStream("SqlMapConfig.xml");
builder = new SqlSessionFactoryBuilder();
factory = builder.build(in);
// 2.使用SqlSessionFactory生产SqlSession对象
session = factory.openSession(true);
// 3.使用SqlSession创建dao接口的代理对象
userDao = session.getMapper(UserDao.class);
}
@After
public void destroy() throws Exception {
// 关闭资源
session.close();
in.close();
}
/**
* 根据id查询用户
*
* @throws Exception
*/
@Test
public void testFindOneById() throws Exception {
User user = userDao.findOneById(49);
// 关闭SqlSession和清除SqlSession的缓存都会重新进行数据库的查询
// session.close();
// session.commit();
session.clearCache();
User user01 = userDao.findOneById(49);
// true说明该用户是同一个用户
System.out.println(user == user01);
}
/**
* 根据姓氏模糊查询用户
*
* @throws Exception
*/
@Test
public void testFindAllByName() throws Exception {
List<User> users = userDao.findAllByName("覃");
for (User u : users) {
System.out.println(u);
}
}
/**
* 根据条件执行查询所有方法
*
* @throws Exception
*/
@Test
public void testFindAllByCondition() throws Exception {
User user = new User(null, "HC", new Date(), "男", "湖南岳阳");
List<User> users = userDao.findAllByCondition(user);
for (User u : users) {
System.out.println(u);
}
}
/**
* 根据条件执行查询所有方法
*
* @throws Exception
*/
@Test
public void testFindAllByQueryVo() throws Exception {
QueryVo queryVo = new QueryVo();
List<Integer> list = new ArrayList<>();
list.add(41);
list.add(42);
queryVo.setIds(list);
List<User> users = userDao.findAllByQueryVo(queryVo);
for (User u : users) {
System.out.println(u);
}
}
/**
* 查询所有用户及其账户信息
*
* @throws Exception
*/
@Test
public void testFindAllAndAccount() throws Exception {
List<User> users = userDao.findAllAndAccount();
for (User u : users) {
System.out.println(u);
}
}
/**
* 查询所有用户及其角色信息
*
* @throws Exception
*/
@Test
public void testFindAllAndRole() throws Exception {
List<User> users = userDao.findAllAndRole();
for (User u : users) {
System.out.println(u);
}
}
/**
* 执行查询所有方法
*
* @throws Exception
*/
@Test
public void testFindAll() throws Exception {
List<User> users = userDao.findAll();
for (User u : users) {
System.out.println(u);
}
}
/**
* 查询总的记录数
*
* @throws Exception
*/
@Test
public void testFindTotal() throws Exception {
System.out.println(userDao.findTotal());
}
/**
* 插入一个User对象
*/
@Test
public void testInsertOne() {
User user = new User(null, "HC", new Date(), "男", "湖南岳阳");
System.out.println("添加用户前:" + user.toString());
userDao.insertOne(user);
session.commit(); // 提交事务
System.out.println("添加用户后:" + user.toString());
}
/**
* 更新一个User对象
*/
@Test
public void testUpdateOne() {
User user = new User(49, "覃杰", new Date(), "男", "湖北恩施");
userDao.updateOne(user);
session.commit();
}
/**
* 删除一个User对象
*/
@Test
public void testDeleteOne() {
userDao.deleteOne(48);
session.commit();
}
@Test
public void testTwoCache() {
SqlSession session1 = factory.openSession();
UserDao userDao1 = session1.getMapper(UserDao.class);
User user01 = userDao1.findOneById(49);
session1.clearCache();
SqlSession session2 = factory.openSession();
UserDao userDao2 = session1.getMapper(UserDao.class);
User user02 = userDao2.findOneById(49);
session2.clearCache();
// 尽管使用了二级缓存,但是这二者也不是同一个对象,因为从SqlSessionFactory拿出的数据创建一个新的对象返回
System.out.println(user01 == user02);
}
}
4 Mapper.xml探索
MyBatis
的用法参见官方文档。
4.1 占位符${}
和#{}
参照此文。
4.2 resultMap
resultType
:指定结果集的类型,支持基本类型和实体类类型。注意,当实体类的属性名和查询的列名不一致时,封装会失败。
<select id="findAll" resultType="user">
select *
from user;
</select>
resultMap
:建立查询的列名和实体类的属性名称不一致时建立对应关系,多对对查询时都会使用到resultMap
。假设如下关系:一个套餐中包含多个检查组,而一个检查组中包含多个检查项。现在的要求是需要通过套餐的id
值查询出所有的检查组信息和检查项信息。
<resultMap id="baseResultMap" type="com.hc.pojo.Setmeal">
<id column="id" property="id"></id>
<result column="name" property="name"/>
<result column="code" property="code"/>
<result column="helpCode" property="helpCode"/>
<result column="sex" property="sex"/>
<result column="age" property="age"/>
<result column="price" property="price"/>
<result column="remark" property="remark"/>
<result column="attention" property="attention"/>
<result column="img" property="img"/>
</resultMap>
<resultMap id="findByIdResultMap" type="com.hc.pojo.Setmeal" extends="baseResultMap">
<!--
collection是用于建立一对多中集合属性的对应关系
property用于指定“com.hc.pojo.Setmeal”中的属性
ofType用于指定集合元素的数据类型
column用于指定需要传入select="com.hc.dao.CheckGroupDao.findCheckGroupById"查询的参数
-->
<collection property="checkGroups" javaType="ArrayList" ofType="com.hc.pojo.CheckGroup" column="id"
select="com.hc.dao.CheckGroupDao.findCheckGroupById"/>
</resultMap>
<!--根据id查询套餐-->
<select id="findById" parameterType="int" resultMap="findByIdResultMap">
select *
from t_setmeal
where id = #{id}
</select>
<resultMap id="baseResultMap" type="com.hc.pojo.CheckGroup">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="code" property="code"/>
<result column="helpCode" property="helpCode"/>
<result column="sex" property="sex"/>
<result column="remark" property="remark"/>
<result column="attention" property="attention"/>
</resultMap>
<resultMap id="findByIdResultMap" type="com.hc.pojo.CheckGroup">
<collection property="checkItems" javaType="ArrayList" ofType="com.hc.pojo.CheckItem" column="id"
select="com.hc.dao.CheckItemDao.findCheckItemById"/>
</resultMap>
<!--根据套餐-检查组中间表查询-->
<select id="findCheckGroupById" resultMap="findByIdResultMap">
select *
from t_checkgroup
where id in (select checkgroup_id
from t_setmeal_checkgroup
where setmeal_id = #{id}
)
</select>
<select id="findCheckItemById" resultType="com.hc.pojo.CheckItem">
select *
from t_checkitem
where id in (select checkitem_id from t_checkgroup_checkitem where checkgroup_id = #{id})
</select>
4.3 MyBatis连接池
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。它可以减少我们获取连接所消耗的时间,连接池里面有若干连接,每个线程需要连接的时候就取一个出来,用完了就把连接放回去,连接池本质上就是一个存储连接的容器,也可以理解为一个集合对象且该集合必须是线程安全的,不能两个线程拿到同一个连接。同时,该集合还要实现队列的特性——先进先出。
4.3.1 分类
Mybatis
连接池一般分为以下三类:
UNPOOLED
:不使用连接池的数据源,MyBatis
会创建UnpooledDataSource
实例。
POOLED
:使用连接池的数据源,MyBatis
会创建PooledDataSource
实例。
JNDI
: 使用JNDI
实现数据源。
4.3.2 配置
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<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>
</environment>
</environments>
4.3.3 分析
MyBatis
配置使用连接池的数据源后,会创建PooledDataSource
实例。
可以看到,PooledDataSource
实例中持有一个UnpooledDataSource
,也就是说当PooledDataSource
需要创建java.sql.Connection
对象时,还是通过UnpooledDataSource
来创建,PooledDataSource
只是提供一种缓存连接池机制。注意,只有当SqlSession
对象要去执行SQL
语句时,MyBatis
才会去调用DataSource
对象创建java.sql.Connection
,毕竟数据库连接是比较珍贵的资源。
4.4 主键自增与返回、缓存、动态SQL和延迟加载
参照此文。