- 概念
- 架构
- 入门案例
- Mapper代理开发方式
- SqlMapConfig.xml
- ResultType和ResultMap
- 动态sql
- 高级映射
- 延时加载
- Mybatis缓存
- Mybatis和Spring整合
概念
Mybatis是Java的持久层框架,是对jdbc的封装,它使得开发者只需要关注sql本身,而不需要花费精力在注册驱动、创建连接、创建statement、设置参数、解析结果等原生jdbc需要完成的操作上。
项目地址:https://github.com/mybatis/mybatis-3。
架构
Mybatis的整体架构如下(参考https://blog.csdn.net/hellozpc/article/details/80878563):
- SqlMapConfig.xml:全局配置文件,配置运行环境,包括数据源和事物,以及映射文件mapper.xml;
- SqlSessionFactory:会话工厂,通过全局配置文件可以创建,用于创建SqlSession;
- SqlSession:面向用户的接口,操作数据库;
-
Executor:执行器 (接口),SqlSession底层使用执行器操作数据库,有两个实现类:默认执行器和缓存执行器;
- MappedStatement:Mybatis的封装对象,封装sql语句,包含输入和输出映射;
入门案例
引入依赖:
<?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>cn.adoredu</groupId>
<artifactId>mybatis-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.7</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
PO类:
package cn.adoredu.mybatis.po;
import java.util.Date;
public class User {
private int id;
private String username;
private String sex;
private Date birthday;
private String address;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", sex='" + sex + '\'' +
", birthday=" + birthday +
", address='" + address + '\'' +
'}';
}
}
全局配置文件:
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 和spring整合后 environments配置将废除-->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理-->
<transactionManager type="JDBC" />
<!-- 数据库连接池-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis-demo"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 加载mapper.xml -->
<mappers>
<mapper resource="sqlmap/User.xml" />
</mappers>
</configuration>
映射文件:
<?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">
<!-- 命名空间,方便对sql进行统一管理。 -->
<mapper namespace="usermapper">
<!-- mapper.xml下面封装多个sql,执行每个sql时都封装为MappedStatement对象。mapper.xml以statement为单位管理sql语句 -->
<!--
id:statement的唯一标识
#{}:表示一个占位符。当输入类型为简单类型时,里面名称任意
parameterType:输入参数的类型
resultType:输出结果的类型(list和单条都是单个po类型)
-->
<select id="findUserById" parameterType="int" resultType="cn.adoredu.mybatis.po.User">
SELECT * FROM user WHERE id = ${value}
</select>
<select id="findUserByName" parameterType="java.lang.String" resultType="cn.adoredu.mybatis.po.User">
SELECT * FROM user WHERE username like '%${value}%'
</select>
<!--
#{username}表示从parameterType中获取pojo属性值(OGNL解析),这里使用${}也可以
selectKey:用于主键返回
order:设置selectKey中sql相对于insert语句的执行顺序
keyProperty:主键字段
resultType:select LAST_INSERT_ID()的类型
-->
<insert id="insertUser" parameterType="cn.adoredu.mybatis.po.User">
<selectKey keyProperty="id" order="AFTER" resultType="int">
SELECT LAST_INSERT_ID()
</selectKey>
INSERT INTO user(username, birthday, sex, address) VALUES(#{username}, #{birthday}, #{sex}, #{address})
</insert>
<!-- 使用uuid主键的插入方式-->
<!--
<insert id="insertUser" parameterType="cn.adoredu.mybatis.po.User">
<selectKey keyProperty="id" order="BEFORE" resultType="String">
SELECT uuid()
</selectKey>
INSERT INTO user(id,username,birthday,sex,address) VALUES (#{id}, #{username}, #{birthday}, #{sex}, #{address})
</insert>
-->
<delete id="deleteById" parameterType="int">
DELETE FROM user WHERE id = #{id}
</delete>
<!-- 传入的User对象必须包含id属性 -->
<update id="updateUser" parameterType="cn.adoredu.mybatis.po.User">
UPDATE user SET username=#{username}, birthday=#{birthday}, sex=#{sex}, address=#{address} WHERE id=#{id}
</update>
</mapper>
测试类:
package cn.adoredu.myBatis;
import cn.adoredu.mybatis.po.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.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
public class UserTest {
private SqlSessionFactory sqlSessionFactory;
// 创建工厂
@Before
public void init() throws IOException {
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
// 根据id查询用户(单条记录)
@Test
public void testOne() {
// 创建SqlSesion
SqlSession sqlSession = sqlSessionFactory.openSession();
// 操作数据库 1. statement位置(namespace.id),2. 传入的参数
User user = null;
try {
user = sqlSession.selectOne("usermapper.findUserById", 1);
} catch (Exception e) {
e.printStackTrace();
}finally {
sqlSession.close();
}
System.out.println(user);
}
// 根据名称查询(多条记录)
@Test
public void testList() {
SqlSession sqlSession = sqlSessionFactory.openSession();
List<User> userList = null;
try {
userList = sqlSession.selectList("usermapper.findUserByName", "admin");
} catch (Exception e) {
e.printStackTrace();
}finally {
sqlSession.close();
}
System.out.println(userList);
}
@Test
public void testInsert() {
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setUsername("Tom");
user.setAddress("sz");
user.setBirthday(new Date());
user.setSex("1");
try {
sqlSession.insert("usermapper.insertUser", user);
sqlSession.commit();
} catch (Exception e) {
e.printStackTrace();
}finally {
sqlSession.close();
}
System.out.println(user.getId());
}
@Test
public void testDelete() {
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
sqlSession.delete("usermapper.deleteById", 28);
sqlSession.commit();
} catch (Exception e) {
e.printStackTrace();
}finally {
sqlSession.close();
}
}
@Test
public void testUpdate() {
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setId(27);
user.setBirthday(new Date());
user.setSex("2");
user.setUsername("Jerry");
user.setAddress("bj");
try {
sqlSession.update("usermapper.updateUser", user);
sqlSession.commit();
} catch (Exception e) {
e.printStackTrace();
}finally {
sqlSession.close();
}
}
}
注意:
- #{}:表示占位符,mybatis自动进行参数的java类型到jdbc类型的转换(如字符串时自动添加引号)。输入参数为简单类型时,#{}里面名称任意;
- ${}:表示sql拼接,使用${}时会将参数不加任何修饰的传入sql(这种方式无法防止sql注入)。输入参数为简单类型时,${}里面只能是value;
步骤总结:
- 编写全局配置文件SqlMapConfig.xml(名称不固定);
- 编写Mapper.xml,定义statement;
- 通过配置文件创建SqlSessionFactory;
- 通过SqlSessionFactory获取SqlSession;
- 通过SqlSession操作数据库,包括增删改要执行commit;
- SqlSession.close();
Mapper代理开发方式
实际项目中,如果Dao的实现类采用上面方式进行开发,实际上仍有大量重复代码。Mybatis提供Mapper代理的开发方式,代码中只需要按一定规则编码Mapper接口和Mapper.xml,Mybatis自动创建接口代理类来完成相应功能。配置文件一般放在mapper目录下,如mapper/UserMapper.xml。规则包括:
- Mapper.xml中namespace为Mapper接口的全限定名(全路径);
- Mapper.xml中statement的id为Mapper接口的方法名;
- Mapper.xml中statement的parameterType和Mapper接口方法的输入参数类型一致;
- Mapper.xml的statement的resultType和Mapper接口方法的返回值类型一致;
使用方式:使用sqlSession.getMapper(UserMapper.class)
获得代理对象,调用代理对象的方法即可。
映射文件:
<?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.adoredu.mybatis.mapper.UserMapper">
<select id="findUserById" parameterType="int" resultType="cn.adoredu.mybatis.po.User">
SELECT * FROM user WHERE id = ${value}
</select>
<select id="findUserByName" parameterType="java.lang.String" resultType="cn.adoredu.mybatis.po.User">
SELECT * FROM user WHERE username like '%${value}%'
</select>
<insert id="insertUser" parameterType="cn.adoredu.mybatis.po.User">
<selectKey keyProperty="id" order="AFTER" resultType="int">
SELECT LAST_INSERT_ID()
</selectKey>
INSERT INTO user(username, birthday, sex, address) VALUES(#{username}, #{birthday}, #{sex}, #{address})
</insert>
<delete id="deleteById" parameterType="int">
DELETE FROM user WHERE id = #{id}
</delete>
<update id="updateUser" parameterType="cn.adoredu.mybatis.po.User">
UPDATE user SET username=#{username}, birthday=#{birthday}, sex=#{sex}, address=#{address} WHERE id=#{id}
</update>
</mapper>
接口:
package cn.adoredu.mybatis.mapper;
import cn.adoredu.mybatis.po.User;
import java.util.List;
public interface UserMapper {
User findUserById(int id) throws Exception;
List<User> findUserByName(String username) throws Exception;
void insertUser(User user) throws Exception;
void deleteById(int id) throws Exception;
void updateUser(User user) throws Exception;
}
测试类:
package cn.adoredu.myBatis;
import cn.adoredu.mybatis.mapper.UserMapper;
import cn.adoredu.mybatis.po.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.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
public class UserMapperTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void init() throws IOException {
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testOne() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
// 创建代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 调用代理对象的方法
User user = userMapper.findUserById(1);
sqlSession.close();
System.out.println(user);
}
@Test
public void testDelete() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.deleteById(27);
sqlSession.commit();
sqlSession.close();
}
}
SqlMapConfig.xml
Mybatis全局配置文件中支持配置的内容和顺序为:
- properties:属性
- settings:全局配置
- typeAliases:类型别名
- typeHandlers:类型处理器
- objectFactory:对象工厂
- plugins:插件
- enviromments:环境集合属性对象
- environment:环境
- transactionManager:事务管理
- dataSource:数据源
- environment:环境
- mappers:映射文件
- properties:
Properties里面可以定义属性,也可以指定properties文件。顺序上,定义的属性首先被读取,然后是引入的文件,最后是parameterType传递的属性,如果重名则后面覆盖前面。一般使用resource或url引入的properties文件而不使用内部定义属性。常见的配置如数据源信息:
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="db.properties" />
<!-- 和spring整合后 environments配置将废除-->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理-->
<transactionManager type="JDBC" />
<!-- 数据库连接池-->
<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>
<!-- 加载mapper.xml -->
<mappers>
<package name="cn.adoredu.mybatis.mapper"/>
</mappers>
</configuration>
- settings:
全局配置参数,可以设置最大线程数,最大等待时间等。
- typeAliases:
可以将parameterType、resultType中指定的类型通过别名使用。例如:
<typeAliases>
<!-- 定义单个别名-->
<typeAlias type="cn.adoredu.mybatis.po.User" alias="user" />
<!-- 批量定义别名,指定包路径,自动扫描包下面的pojo,别名默认为类名(大小写首字母均可)-->
<package name="cn.adoredu.mybatis.po"/>
</typeAliases>
- typeHandlers:
将java类型和jdbc类型进行映射(mybatis提供很多默认映射)。
- mappers:
mappers配置映射文件除使用resource外,还可以使用class属性或者package标签配置:
<mappers>
<!-- <mapper resource="mapper/UserMapper.xml" />-->
<!-- <mapper class="cn.adoredu.mybatis.mapper.UserMapper" /> -->
<!-- 通过package会自动扫描包下面的mapper接口和配置文件 -->
<package name="cn.adoredu.mybatis.mapper" />
</mappers>
注意,若使用calss属性或package标签,则要求xml和接口同名且在同一目录下。另外如果使用maven构建项目,需要在pom文件中添加:
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
ResultType和ResultMap
resultType:指定输出结果类型,将sql查询结果映射到Java对象。映射pojo时要注意查询sql列名和对象属性名要一致,否则无法映射,值为null;
resultMap:通过列名配置可以将查询名不一致的对象映射为Java对象;
映射配置:
<?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.adoredu.mybatis.mapper.UserMapper">
<!--
id:resultMap的文艺标识
type:要映射的pojo(全局配置文件中已定义别名)
-->
<resultMap id="userListResultMap" type="user">
<!--
id:pojo的主键
column:结果集列名
property:pojo属性名
-->
<id column="_id" property="id"></id>
<!--
result:普通列的映射配置
-->
<result column="_username" property="username"></result>
<result column="_birthday" property="birthday"></result>
<result column="_sex" property="_sex"></result>
<result column="_address" property="address"></result>
</resultMap>
<!-- 映射配置和使用在同一xml中,resultMap属性直接使用id。否则为namespace.id -->
<select id="findUserByResultMap" parameterType="String" resultMap="userListResultMap">
SELECT * FROM user WHERE username like '%${value}%'
</select>
<!-- ... -->
</mapper>
动态sql
入门案例中,sql语句比较固定,不够灵活。如查询列表接口只能模糊查询username,而无法做到根据参数灵活查询。
- if-where:
<select id="findUserList" parameterType="user" resultType="user">
SELECT * FROM user
<where>
<if test="username != null and username != ''">
username like '%${username}'
</if>
<if test="sex != null and sex != ''">
sex = #{sex}
</if>
</where>
</select>
通过配置log4j查看sql语句可以看出,这种方式可以比较灵活的实现根据条件查询。
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
log4j.properties:
# Global logging configuration\uff0c\u5efa\u8bae\u5f00\u53d1\u73af\u5883\u4e2d\u8981\u7528debug
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
- sql片段:
上述方式比较灵活的实现了多条件查询。实际上,可以把条件片段抽取分离,这样比较公共的片段可以用于多个sql语句。
<?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.adoredu.mybatis.mapper.UserMapper">
<sql id="query_user_where">
<if test="username != null and username != ''">
AND username like '%${username}'
</if>
<if test="sex != null and sex != ''">
AND sex = #{sex}
</if>
</sql>
<select id="findUserList" parameterType="user" resultType="user">
SELECT * FROM user
<where>
<!-- 同xml内,直接使用id引入。跨文件时,使用namespace.id -->
<include refid="query_user_where"/>
<!-- 下面可以继续跟其他类的sql片段作为条件 -->
</where>
</select>
</mapper>
- foreach
加入现查询条件为id在某范围内时,可以使用foreach实现。
首先要定义一个QueryVo类,包含ids属性:
package cn.adoredu.mybatis.po;
import java.util.List;
public class UserQueryVo {
private User user;
private List<String> ids;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public List<String> getIds() {
return ids;
}
public void setIds(List<String> ids) {
this.ids = ids;
}
}
<?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.adoredu.mybatis.mapper.UserMapper">
<sql id="query_user_where">
<if test="user!=null">
<if test="user.username != null and user.username != ''">
AND username like '%${user.username}'
</if>
<if test="user.sex != null and user.sex != ''">
AND sex = #{user.sex}
</if>
</if>
<!--
collection:集合的属性;
open:开始拼接字符串;
close:结束拼接字符串;
item:每次循环到的对象;
separator:分隔字符串;
-->
<!-- and id in (1,2,3) -->
<foreach collection="ids" open="AND id IN (" close=")" separator="," item="id">
#{id}
</foreach>
<!--and (id=1 or id=2 or id=3)-->
<!--<foreach collection="ids" open="and (" close=")" separator="OR" item="id">
id=#{id}
</foreach>-->
</sql>
<select id="findUserList" parameterType="userQueryVo" resultType="user">
SELECT * FROM user
<where>
<include refid="query_user_where"/>
</where>
</select>
</mapper>
高级映射
表:用户表、订单表、订单明细表、商品表(参考:https://github.com/AdoredU/mybatis-demo)。
一对一查询
- resultType实现一对一查询
需求:查询订单,关联查询用户。
分析:因为resultType要查询字段名和属性名一致,因此创建结果po类,po类可以继承查询属性多的类简化。
package cn.adoredu.mybatis.po;
public class OrderCustom extends Orders {
private String username;
private String sex;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "OrderCustom{" +
"username='" + username + '\'' +
", sex='" + sex + '\'' +
'}';
}
}
创建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.adoredu.mybatis.mapper.OrderMapperCustom">
<select id="findOrderUserList" resultType="orderCustom">
SELECT orders.*, user.username, user.sex FROM orders, user WHERE orders.user_id = user.id
</select>
</mapper>
- resultMap实现一对一查询
resultMap支持将一对一关联查询的记录映射到pojo中。首先在Orders类中添加User属性:
package cn.adoredu.mybatis.po;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
public class Orders implements Serializable {
private Integer id;
private Integer userId;
private String number;
private Date createtime;
private String note;
// 因为是一对一,所以这里直接定义单个User而不是List<User>
private User user;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number == null ? null : number.trim();
}
public Date getCreatetime() {
return createtime;
}
public void setCreatetime(Date createtime) {
this.createtime = createtime;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note == null ? null : note.trim();
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
映射配置:
<?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.adoredu.mybatis.mapper.OrderMapperCustom">
<resultMap id="ordersUserResultMap" type="orders">
<id column="id" property="id" />
<result column="user_id" property="userId" />
<result column="number" property="number" />
<result column="createtime" property="createtime" />
<result column="note" property="note" />
<!-- 使用association完成关联信息查询 -->
<!-- property:属性名, javaType:java类型 -->
<association property="user" javaType="user">
<!-- id:关联信息的唯一标识 -->
<id column="user_id" property="id" />
<result column="username" property="username" />
<result column="sex" property="sex" />
</association>
</resultMap>
<select id="findOrderUserList" resultType="orderCustom">
SELECT orders.*, user.username, user.sex FROM orders, user WHERE orders.user_id = user.id
</select>
<select id="findOrderUserResultMap" resultMap="ordersUserResultMap">
SELECT orders.*, user.username, user.sex FROM orders, user WHERE orders.user_id = user.id
</select>
</mapper>
一对多查询(resultMap实现)
需求:查询订单信息关联查询订单明细。
使用resultMap实现一对多要在一类中创建多类的集合:
package cn.adoredu.mybatis.po;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
public class Orders implements Serializable {
private Integer id;
private Integer userId;
private String number;
private Date createtime;
private String note;
private User user;
private List<Orderdetail> orderDetailList;
// ...getters/setters
}
配置(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.adoredu.mybatis.mapper.OrderMapperCustom">
<resultMap id="ordersUserResultMap" type="orders">
<id column="id" property="id" />
<result column="user_id" property="userId" />
<result column="number" property="number" />
<result column="createtime" property="createtime" />
<result column="note" property="note" />
<!-- 使用association完成关联信息查询 -->
<!-- property:属性名, javaType:java类型 -->
<association property="user" javaType="user">
<!-- id:关联信息的唯一标识 -->
<id column="user_id" property="id" />
<result column="username" property="username" />
<result column="sex" property="sex" />
</association>
</resultMap>
<!-- 使用继承简化order和user部分的写法 -->
<resultMap id="ordersAndOrderDetails" type="orders" extends="ordersUserResultMap">
<!-- 集合时使用oftype属性,值为pojo类型 -->
<collection property="orderDetailList" ofType="orderDetail">
<!-- property:oftype的pojo的属性 -->
<id column="orderdetail_id" property="id" />
<result column="items_num" property="itemsNum" />
<result column="items_id" property="itemsId" />
<result />
</collection>
</resultMap>
<select id="findOrderUserList" resultType="orderCustom">
SELECT orders.*, user.username, user.sex FROM orders, user WHERE orders.user_id = user.id
</select>
<select id="findOrderUserResultMap" resultMap="ordersUserResultMap">
SELECT orders.*, user.username, user.sex FROM orders, user WHERE orders.user_id = user.id
</select>
<select id="findOrdersOrderDetailMap" resultMap="ordersAndOrderDetails">
SELECT
orders.*, USER .username,
USER .sex,
orderdetail.id orderdetail_id,
orderdetail.items_id,
orderdetail.items_num
FROM
orders,
USER,
orderdetail
WHERE
orders.user_id = USER .id
AND orders.id = orderdetail.orders_id
</select>
</mapper>
注意:如果直接执行上面一对多sql语句,会返回主查询对象重复的结果。而使用mybatis一对多resultMap配置后,一的对象是不重复的,而多的对象则作为一对象的集合。
延时加载
需求:查询订单关联查询用户信息。
要使用延时加载,首先要在mybatis全局配置文件中开启延时加载:
...
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
...
注意需要cglib包:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
配置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.adoredu.mybatis.mapper.OrderMapperCustom">
<resultMap id="ordersUserResultMap" type="orders">
<id column="id" property="id" />
<result column="user_id" property="userId" />
<result column="number" property="number" />
<result column="createtime" property="createtime" />
<result column="note" property="note" />
<!-- 使用association完成关联信息查询 -->
<!-- property:属性名, javaType:java类型 -->
<association property="user" javaType="user">
<!-- id:关联信息的唯一标识 -->
<id column="user_id" property="id" />
<result column="username" property="username" />
<result column="sex" property="sex" />
</association>
</resultMap>
<!-- 使用继承简化order和user部分的写法 -->
<resultMap id="ordersAndOrderDetails" type="orders" extends="ordersUserResultMap">
<!-- 集合时使用oftype属性,值为pojo类型 -->
<collection property="orderDetailList" ofType="orderDetail">
<!-- property:oftype的pojo的属性 -->
<id column="orderdetail_id" property="id" />
<result column="items_num" property="itemsNum" />
<result column="items_id" property="itemsId" />
</collection>
</resultMap>
<!-- 一对一延时加载配置 -->
<!-- 用户信息仍是orders的一个属性,查的还是orders,type为orders -->
<resultMap id="orderCustomLazyLoading" type="orders">
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<association property="user" select="cn.adoredu.mybatis.mapper.UserMapper.findUserById" column="user_id" />
</resultMap>
<select id="findOrderUserList" resultType="orderCustom">
SELECT orders.*, user.username, user.sex FROM orders, user WHERE orders.user_id = user.id
</select>
<select id="findOrderUserResultMap" resultMap="ordersUserResultMap">
SELECT orders.*, user.username, user.sex FROM orders, user WHERE orders.user_id = user.id
</select>
<select id="findOrdersOrderDetailMap" resultMap="ordersAndOrderDetails">
SELECT
orders.*, USER .username,
USER .sex,
orderdetail.id orderdetail_id,
orderdetail.items_id,
orderdetail.items_num
FROM
orders,
USER,
orderdetail
WHERE
orders.user_id = USER .id
AND orders.id = orderdetail.orders_id
</select>
<select id="findOrdersUserLazyLoading" resultMap="orderCustomLazyLoading">
SELECT * FROM orders
</select>
</mapper>
此时在查询时只会执行查询订单语句,只有当使用到订单信息中用户时才会执行查询用户操作。
实际上,mybatis的延时加载无法作用到页面端,因为它只在同一会话中有效,即可以理解为它只作用至service曾。因此实际项目中使用并不多,具体看需求。
Mybatis缓存
Mybatis缓存可以分为一级缓存和二级缓存两种。
一级缓存:sqlSession级别的,即在同一个sqlSession内有效。当首次执行查询时,会从数据库查询数据,并把结果缓存起来,再次查询时不会向数据库发送请求而是直接从缓存中查询。但执行commit操作(增、删、改)时,会把缓存区清空。
Mybatis默认支持一级缓存,无需配置。但需要注意的是,当和spring整合使用时,不支持一级缓存,这是因为spring按照模板生成mapper代理对象,会统一执行关闭sqlSession。
二级缓存:跨sqlSession,是mapper级别的,同一个命名空间下有效,mapper以命名空间为单位创建缓存Map。二级缓存需要配置:
-
在全局配置文件中settings下配置开启缓存:
<setting name="cacheEnabled" value="true" />
; - 映射文件中配置cache:
<cache />
; - 缓存的pojo类序列化(pojo中若有类属性,也要序列化);
注意:
-
通过跟踪代码(sqlSession的select方法)不难发现,mybatis优先查询二级缓存;
- 禁用二级缓存:若对部分sql禁用二级缓存,则在statement中添加属性
userCache="false"
; - 刷新缓存:在增删改的sql中,有默认属性
flushCache="true"
,表示执行时会清空二级缓存,当然可以手动修改为false表示不清空(一般不会有这类需求); - 映射文件中
<cache />
常用属性有flushInterval="60000"
,单位毫秒;
Mybatis和Spring整合
思路:
- spring管理数据源;
- spring管理SqlSessionFactory;
- spring管理mapper/dao对象(包括事务等);
- 添加依赖:mybatis、spring、mysql、mybatis-spring、dbcp
<?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>cn.adoredu</groupId>
<artifactId>mybatis-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<spring.version>4.2.4.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-dbcp/commons-dbcp -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.7</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
</project>
- 配置sqlMapConfig.xml:
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<package name="cn.adoredu.mybatis.po"/>
</typeAliases>
<!-- 加载mapper.xml -->
<mappers>
<!-- 用于原生dao开发 -->
<mapper resource="sqlmap/User.xml" />
<package name="cn.adoredu.mybatis.mapper"/>
</mappers>
</configuration>
- 配置applicationContext.xml:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<!-- 加载配置文件 -->
<context:property-placeholder location="classpath:db.properties" />
<!-- 数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="maxActive" value="10" />
<property name="maxIdle" value="5" />
</bean>
<!-- 配置sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- mybatis配置文件 -->
<property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml" />
</bean>
<!-- mybatis整合spring共三种方式开发 -->
<!-- 方式一:原生dao方式,要配置dao -->
<bean id="userDao" class="cn.adoredu.mybatis.dao.UserDaoImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
<!-- 方式二:使用MapperFactoryBean
MapperFactoryBean:用于生成代理对象。该类本身继承自SqlSessionDaoSupport
此方法每个mapper都要配置
-->
<!--<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="cn.adoredu.mybatis.mapper.UserMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>-->
<!-- 方式三:使用MapperScannerConfigurer
MapperScannerConfigurer:将包下面mapper自动创建代理对象到spring容器中,id为mapper类型首字母小写
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 扫描多个包,逗号拼接 -->
<property name="basePackage" value="cn.adoredu.mybatis.mapper" />
<!-- 注意,这里使用的是sqlSessionFactoryBeanName,后面为value。这是为避免版本遗留问题导致冲突 -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
</beans>
测试类:
package cn.adoredu.myBatis;
import cn.adoredu.mybatis.dao.UserDao;
import cn.adoredu.mybatis.mapper.UserMapper;
import cn.adoredu.mybatis.po.User;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserSpringTest {
private ApplicationContext context;
@Before
public void init() throws Exception {
context = new ClassPathXmlApplicationContext("spring/applicationContext.xml");
}
@Test
public void testDao() throws Exception {
UserDao userDao = (UserDao) context.getBean("userDao");
User user = userDao.findUserById(1);
System.out.println(user);
}
@Test
public void testMapper() throws Exception {
UserMapper userMapper = (UserMapper) context.getBean("userMapper");
User user = userMapper.findUserById(1);
System.out.println(user);
}
}