MyBatis
大家好呀!我是小笙,我中间有1年没有更新文章了,主要忙于毕业和就业相关事情,接下来,我会恢复更新!我们一起努力吧!
概述
MyBatis 是一个持久层的框架(前身是 ibatis,在 ibatis3.x 的时候更名为 MyBatis)
为什么使用 MyBatis 框架?
- 简化以及规范化连接数据库(不需要手动连接数据库)
- 程序不再是以一种硬编码的形式实现(解耦,灵活性高)
MyBatis 核心框架图
简易实现 MyBatis 底层机制
备注:简易实现MyBatis底层机制所用到的表如上述快速入门,实现的是简单查询用户数据
实现框架图
SqlSession 类的设计
SqlSession:主要是用来连接数据库以及操作数据库
public class SqlSession {
/**
* 主要用来连接数据库
*/
private Configuration configuration = new Configuration();
/**
* 主要用来操作数据库
*/
private ExecutorImpl executor = new ExecutorImpl();
/**
* 直接操作数据库来查询用户数据
*/
public <T> T selectOne(String statement, Object parameter) {
try {
return executor.selectOne(statement, parameter);
} catch (SQLException throwables) {
throw new RuntimeException();
}
}
/**
* 返回mapper的动态代理对象
*/
public <T> T getMapper(Class<T> clazz) {
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz},
new MapperProxy(configuration,this,"repo/UserMapper.xml"));
}
}
Configuration 类主要是用来读取配置文件
- 读取config数据库配置文件,获取连接
- 读取mapper接口以及mapper接口对应的xml配置文件信息,封装到MapperBean对象中
/**
* 配置读取类
*/
public class Configuration {
// 日志输出
private static Logger log = Logger.getLogger("com.Altair.myBatis.logs");;
// 获取资源加载类
private static ClassLoader loader = ClassLoader.getSystemClassLoader();
/**
* 读取配置文件
* @param configName 配置文件名字
* @return
*/
public Connection ReadConfig(String configName) {
Connection connection = null;
try {
// 获取 xml 配置文件流
InputStream stream = loader.getResourceAsStream(configName);
// SAXReader 读取 xml 配置文件内容
SAXReader read = new SAXReader();
Document document = read.read(stream);
// 获取 config 根目节点
Element rootElement = document.getRootElement();
if(Constants.DATABASE_CONFIG.equals(rootElement.getName())){
String driver = "";
String url = "";
String username = "";
String password = "";
for (Object o : rootElement.elements(Constants.DATABASE_PROPERTY)) {
Element element = (Element) o;
String name = element.attributeValue("name");
String value = element.attributeValue("value");
if(name == null || value == null){
log.info("异常报错信息:数据库配置参数错误或者不全!");
throw new RuntimeException("异常报错信息:数据库配置参数错误或者不全!");
}
switch (name){
case Constants.DATABASE_PARAM.DRIVER:
driver = value;break;
case Constants.DATABASE_PARAM.URL:
url = value;break;
case Constants.DATABASE_PARAM.USERNAME:
username = value;break;
case Constants.DATABASE_PARAM.PASSWORD:
password = value;break;
default: break;
}
}
connection = getConnection(driver,url,username,password);
}
}catch (Exception e){
log.info("异常报错信息:" + e.getMessage());
throw new RuntimeException("异常报错信息:" + e.getMessage());
}
return connection;
}
/**
* 获取数据库连接
*/
private Connection getConnection(String driver,String url,String username,String password){
if(driver == "" || url == "" || username == "" || password == "") {
log.info("异常报错信息:数据库配置参数不全!");
throw new RuntimeException("异常报错信息:数据库配置参数不全!");
}
try {
Class.forName(driver);
return DriverManager.getConnection(url, username, password);
}catch (Exception e){
log.info("异常报错信息:数据库连接异常!");
throw new RuntimeException("异常报错信息:数据库连接异常!");
}
}
/**
* 读取Mapper.xml文件生成MapperBean对象
* @param filePath xml文件路径
* @return MapperBean对象
*/
public MapperBean readMapper(String filePath){
MapperBean mapperBean = new MapperBean();
// 获取文件流
InputStream stream = loader.getResourceAsStream(filePath);
// SAXReader 读取 xml 配置文件内容
SAXReader saxReader = new SAXReader();
Document doc = null;
try {
doc = saxReader.read(stream);
} catch (DocumentException e) {
throw new RuntimeException("读取xml配置文件失败!");
}
// 获取 mapper.xml 根目节点
Element root = doc.getRootElement();
// 对应Mapper接口路径
String namespace = root.attributeValue("namespace").trim();
mapperBean.setInterfacePath(namespace);
// 获取迭代器-遍历所有节点
Iterator iterator = root.elementIterator();
List<Function> funs = new ArrayList<>();
while(iterator.hasNext()){
Element node = (Element)iterator.next();
Function function = new Function();
function.setSqlType(node.getName());
function.setSql(node.getTextTrim());
function.setFuncName(node.attributeValue("id").trim());
String parameterType = node.attributeValue("parameterType");
if(parameterType != null){
Class<?> aClass = null;
try {
aClass = Class.forName(parameterType.trim());
function.setParamster(aClass.newInstance());
} catch (Exception e) {
throw new RuntimeException("传入参数对象异常!"+ e.getMessage());
}
}
String resultType = node.attributeValue("resultType");
if(resultType != null){
Class<?> aClass = null;
try {
aClass = Class.forName(resultType.trim());
function.setResultType(aClass.newInstance());
} catch (Exception e) {
throw new RuntimeException("返回参数对象异常!" + e.getMessage());
}
}
funs.add(function);
}
mapperBean.setFunctions(funs);
System.out.println(mapperBean);
return mapperBean;
}
}
MapperBean 类的设计
MapperBean 主要用于封装 Mapper接口以及对应xml文件的信息
/**
* 用来连接Mapper接口和Mapper.xml文件
*/
@Data
public class MapperBean {
/**
* 接口全路径
*/
private String interfacePath;
/**
* 接口的方法信息
*/
private List<Function> functions;
}
/**
* 用来记录 Mapper 方法信息
*/
@Data
public class Function {
/**
* sql类型
*/
private String SqlType;
/**
* sql语句
*/
private String sql;
/**
* 方法名
*/
private String funcName;
/**
* 参数类型
*/
private Object paramster;
/**
* 返回类型
*/
private Object resultType;
}
MapperProxy 代理对象的设计
/**
* 简易的代理对象
*/
public class MapperProxy implements InvocationHandler {
private Configuration configuration = null;
private SqlSession sqlSession = null;
private String filePath = "";
public MapperProxy(Configuration configuration, SqlSession sqlSession, String filePath) {
this.configuration = configuration;
this.sqlSession = sqlSession;
this.filePath = filePath;
}
/**
* 返回代理对象
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MapperBean mapperBean = configuration.readMapper(this.filePath);
//判断是否是xml文件对应的接口
if (!method.getDeclaringClass().getName().equals(mapperBean.getInterfacePath())) {
return null;
}
// 取出mapperBean的functions
List<Function> functions = mapperBean.getFunctions();
// 判断当前mapperBean解析对应MappperXML后 , 有方法
if (null != functions && 0 != functions.size()) {
for (Function function : functions) {
// 当前要执行的方法和function.getFuncName()一样
// 说明我们可以从当前遍历的function对象中,取出相应的信息sql, 并执行方法
if(method.getName().equals(function.getFuncName())) {
if("select".equalsIgnoreCase(function.getSqlType())) {
return sqlSession.selectOne(function.getSql(),String.valueOf(args[0]));
}
}
}
}
return null;
}
}
测试类
/**
* 测试用例
*/
public class TestReadMapper {
@Test
public void test(){
SqlSession sqlSession = SessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(53);
System.out.println("user:" + user);
}
}
快速入门
概述:创建、操作 User 用户,也就是俗称的 CRUD,简单上手,了解大致的开发流程
基础环境
引入依赖包
-
引入jar包形式: mybatis jar包地址
-
Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.15</version> </dependency>
创建数据库表和对象
首先创建一个 User 表
create table user
(
id int auto_increment primary key,
name varchar(64) not null default '',
age int not null default '',
email varchar(255) not null default ''
);
User 实体类
@Data
public class User {
private long id;
private int age;
private String name;
private String email;
}
配置连接数据库信息
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置日志输出(可以输出运行了的MySQL语句) -->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!-- 配置别名 -->
<typeAliases>
<typeAlias alias="User" type="com.Al_tair.entity.User"/>
<typeAlias alias="String" type="java.lang.String"/>
<typeAlias alias="Integer" type="java.lang.Integer"/>
<typeAlias alias="List" type="java.util.List"/>
</typeAliases>
<!-- 配置数据库 -->
<environments default="development">
<!-- 配置实物管理器 -->
<environment id="development">
<transactionManager type="JDBC"/>
<!-- 配置数据源 -->
<dataSource type="POOLED">
<!-- 配置驱动 -->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<!-- 配置连接数据库 -->
<property name="url" value="xxxxxx"/>
<property name="username" value="xxx"/>
<property name="password" value="xxxxx"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="./repo/UserMapper.xml"/>
<!-- 使用注解的形式 -->
<mapper class="com.Al_tair.mapper.UserAnnotationMapper"></mapper>
</mappers>
</configuration>
配置xml文件方式(推荐)
UserMapper 接口
public interface UserMapper {
/**
* 添加用户
* @param user 用户信息
*/
public void addUser(User user);
/**
* 删除用户
* @param id 用户id
*/
public void deleteUser(Integer id);
/**
* 更新用户
* @param user 用户信息
*/
public void updateUser(User user);
/**
* 查询用户
* @param id 用户id
*/
public User findUserById(Integer id);
/**
* 查询用户集合
* @param ids 用户id
*/
public List<User> findUsers(Integer[] ids);
}
UserMapper 接口对应 UserMapper.xml 文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace: 指定对应的接口 -->
<mapper namespace="com.Al_tair.mapper.UserMapper">
<!-- id:指定接口对应的方法名 -->
<!-- parameterType:入参数据类型 -->
<insert id="addUser" parameterType="User">
insert into user (name, age, email)
VALUES (#{name},#{age},#{email});
</insert>
<delete id="deleteUser" parameterType="Integer">
delete from user where id = #{id};
</delete>
<update id="updateUser" parameterType="User">
update user set age = #{age},name = #{name},email = #{email} where id = #{id};
</update>
<select id="findUserById" parameterType="Integer" resultType="User">
select * from user where id = #{id};
</select>
</mapper>
测试类
public class quickTest {
private SqlSession sqlSession;
private UserMapper mapper;
@Before
public void init(){
sqlSession = MyBatisUtils.getSqlSession();
mapper = sqlSession.getMapper(UserMapper.class);
}
/**
* 添加用户
*/
@Test
public void test1(){
for (int i = 0; i < 3; i++) {
User user = new User();
user.setName("lns" + i * 2);
user.setAge(i + 10);
user.setEmail("1045645.qq.com");
mapper.addUser(user);
}
if(sqlSession != null){
sqlSession.commit();
sqlSession.close();
}
System.out.println("添加成功...");
}
/**
* 删除用户
*/
@Test
public void test2(){
mapper.deleteUser(44);
mapper.deleteUser(45);
mapper.deleteUser(46);
if(sqlSession != null){
sqlSession.commit();
sqlSession.close();
}
System.out.println("删除成功...");
}
/**
* 修改用户
*/
@Test
public void test3(){
for (int i = 0; i < 3; i++) {
User user = new User();
user.setId(47+i);
user.setName("zlr" + i * 2);
user.setAge(i + 100);
user.setEmail("1045645.google.com");
mapper.updateUser(user);
}
if(sqlSession != null){
sqlSession.commit();
sqlSession.close();
}
System.out.println("修改成功...");
}
/**
* 查询一条用户数据
*/
@Test
public void test4(){
User user = mapper.findUserById(53);
if(sqlSession != null){
sqlSession.close();
}
System.out.println("查询成功,数据:" + user);
}
}
使用注解方式
配置文件需要添加该如下配置
<mappers>
<!-- 使用注解的形式 -->
<mapper class="com.Al_tair.mapper.UserAnnotationMapper"></mapper>
</mappers>
接口采用注解的形式填入SQL语句
public interface UserAnnotationMapper {
/**
* 添加用户
*
* @param user 用户信息
*/
@Insert("insert into user (name, age, email) VALUES (#{name},#{age},#{email});")
public void addUser(User user);
/**
* 删除用户
*
* @param id 用户id
*/
@Delete("delete from user where id = #{id};")
public void deleteUser(Integer id);
/**
* 更新用户
*
* @param user 用户信息
*/
@Update("update user set age = #{age},name = #{name},email = #{email} where id = #{id};")
public void updateUser(User user);
/**
* 查询用户
*
* @param id 用户id
*/
@Select("select * from user where id = #{id};")
public User findUserById(Integer id);
/**
* 查询用户集合
*
* @param ids 用户id
*/
@Select("select * from user where id in #{id};")
public List<User> findUsers(Integer[] ids);
}
测试方法
public class AnnotationTest {
private SqlSession sqlSession;
private UserAnnotationMapper mapper;
@Before
public void init(){
sqlSession = MyBatisUtils.getSqlSession();
mapper = sqlSession.getMapper(UserAnnotationMapper.class);
}
/**
* 测试查询一条用户数据
*/
@Test
public void test(){
User user = mapper.findUserById(53);
if(sqlSession != null){
sqlSession.close();
}
System.out.println("查询成功,数据:" + user);
}
}
注解配置方式的总结
// 增删改查操作
@Insert("SQL语句")
@Delete("SQL语句")
@Update("SQL语句")
@Select("SQL语句")
// id自增长(keyProperty 对应的是对象属性名;keyColumn对应的是表的字段名)
@Options(useGeneratedKeys = true,keyProperty = "id",keyColumn = "id")
映射器
select 元素的属性
属性 | 描述 |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句。 |
parameterType | 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。 |
resultType | 期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。 |
resultMap | 对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。 |
flushCache | 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。 |
useCache | 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。 |
timeout | 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。 |
fetchSize | 这是一个给驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值。 默认值为未设置(unset)(依赖驱动)。 |
statementType | 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 |
resultSetType | FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)。 |
resultOrdered | 这个设置仅针对嵌套结果 select 语句:如果为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false 。 |
resultSets | 这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。 |
insert、update、delete元素的属性
属性 | 描述 |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句。 |
parameterType | 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。 |
flushCache | 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:(对 insert、update 和 delete 语句)true。 |
timeout | 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。 |
statementType | 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 |
useGeneratedKeys | (仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。 |
keyProperty | (仅适用于 insert 和 update)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset )。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
`keyColumn | (仅适用于 insert 和 update)设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
databaseId | 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。 |
parameterType(输入参数类型)
-
传入简单数据类型
-
传入对象数据类型,查询时需要有多个筛选条件
注意:当传入的参数类是 String 时,也可以使用 ${} 来接收参数
<!-- 实现 findUserByNameORId -->
<select id="findUserByNameORId" parameterType="User" resultType="User">
SELECT * FROM user
WHERE id=#{id} OR name=#{name}
</select>
<!-- 模糊查询需要 ${value} 取值-->
<select id="findUserByName" parameterType="String" resultType="User">
SELECT * FROM user
WHERE name LIKE '%${value}%'
</select>
注意:上述传入参数之所以能够简写,是需要配置项扫描对应实体类的路径
<typeAliases>
<package name="com.Al_tair.entity"/>
</typeAliases>
传入和返回 HashMap
<!--
#{id}、#{age} 传入的参数对应 map 里面 key,如果不传值默认为null
-->
<select id="findUserByIdAndAge_PrameterHashMap_ReturnHashMap" parameterType="map" resultType="map">
SELECT id,age FROM user
WHERE id > #{id} AND age > #{age}
</select>
数据库表的字段名和对象属性名不一致解决方法
-
查询出来的字段添加别名来匹配对象属性名
-
通过配置 resutlMap 来映射他们之间的关系
<!-- 表的字段名: user_id、user_name、user_email 对象的属性名:user_id、username、useremail --> <resultMap type="User" id="findAllUserMap"> <result column="user_name" property="username"/> <result column="user_email" property="useremail"/> </resultMap> <select id="findAllUser" resultMap="findAllUserMap" > SELECT * FROM user </select>
动态SQL语句
动态 SQL 常用标签
if
<!-- 如果用户的年龄不大于0则输出所有用户 ; 反之用户输出的年龄大于0,则按照输入的年龄进行过滤 -->
<select id="findUserByAge" parameterType="Integer" resultType="User">
SELECT * FROM user WHERE 1 = 1
<!-- test 里面的 age 是来自传入的参数,但是想要取到值,传入的参数需要加上注解 @param(age) -->
<if test="age >= 0">
AND age > #{age}
</if>
</select>
where
<!-- 过滤查询用户信息 -->
<select id="findUserByAgeAndName" parameterType="User" resultType="User">
SELECT * FROM user
<where>
<if test = "age >= 0">
And age >= #{age}
</if>
<if test="name != null and name != '' ">
And name = #{name}
</if>
</where>
</select>
choose/when/otherwise
<!-- 过滤查询用户信息 -->
<select id="findUserByAgeOrName" parameterType="User" resultType="User">
SELECT * FROM user WHERE
<choose>
<when test="age >= 0">
age >= #{age}
</when>
<otherwise>
name = #{name}
</otherwise>
</choose>
</select>
foreach
<!-- 过滤查询用户信息 -->
<select id="findUserByIds" parameterType="Map" resultType="User">
SELECT * FROM user
<if test="ids != null and ids != ''">
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</if>
</select>
trim
替换关键字/定制元素的功能(扩展了 where 标签的能力)
<!-- 过滤查询用户信息 -->
<select id="findUserByAgeAndName" parameterType="User" resultType="User">
SELECT * FROM user
<!-- trim 标签能够加前缀 where 或者去掉对于的 AND|OR|XXX 的前缀 -->
<trim prefix="WHERE" prefixOverrides="AND|OR|XXX"
<if test = "age >= 0">
And age >= #{age}
</if>
<if test="name != null and name != '' ">
And name = #{name}
</if>
</trim>
</select>
set
在 update 的 set 中,可以保证进入 set 标签的属性被修改,而没有进入 set 的,保持原来的值
<!-- 更新用户信息 -->
<update id="updateUserInfo" parameterType="Map">
UPDATE user
<set>
<if test="age >= 0">
age = #{age},
</if>
<if test="name != null and name != ''">
name = #{name},
</if>
<if test="email != null and email != ''">
email = #{email}
</if>
</set>
WHERE id = #{id}
</update>
一对一映射
配置Mapper.xml
// 用户类
@Setter
@Getter
@ToString
public class User {
private String id;
private int age;
private String name;
private String email;
private IdCard idCard;
private String cardId;
}
// idCard类
@Setter
@Getter
@ToString
public class IdCard {
private String id;
private String userId;
private String idCard;
}
第一种方法
<!-- 映射返回的结果 -->
<resultMap id="userResultMap" type="User">
<!-- 用<id 标记主键会优化性能 -->
<!-- <result property="id" column="id"/> -->
<id property="id" column="id"/>
<result property="age" column="age"/>
<result property="name" column="name"/>
<result property="email" column="email"/>
<association property="idCard" javaType="IdCard">
<result property="id" column="id"/>
<result property="userId" column="userId"/>
<result property="idCard" column="idCard"/>
</association>
</resultMap>
<!-- 查询用户信息 -->
<select id="getUserById" parameterType="String" resultMap="userResultMap">
SELECT * FROM user
LEFT JOIN idCard ON idCard.userId = user.id
WHERE user.id = #{id}
</select>
第二种方式
<!-- 映射返回的结果 -->
<resultMap id="userResultMap2" type="User">
<!-- 用<id 标记主键会优化性能 -->
<id property="id" column="id"/>
<result property="age" column="age"/>
<result property="name" column="name"/>
<result property="email" column="email"/>
<!-- cardId 为查询出来的 IdCard 表的 id 数据-->
<association property="idCard" column="cardId" select="com.Al_tair.mapper.IdCardMapper.getIdCardById"/>
</resultMap>
<select id="getUserById2" parameterType="String" resultMap="userResultMap2">
SELECT * FROM user WHERE user.id = #{id}
</select>
注解的方式
/**
* 获得idCard
*/
@Select("SELECT * FROM idCard WHERE id = #{id}")
public IdCard findIdCardById(String id);
/**
* 查询用户
*/
@Select("select * from user where id = #{id}")
@Results({
@Result(id = true,property = "id",column = "id"),
@Result(property = "age",column = "age"),
@Result(property = "name",column = "name"),
@Result(property = "email",column = "email"),
@Result(property = "idCard",column = "cardId",
one = @One(select = "com.Al_tair.mapper.IdCardMapper.findIdCardById"))
})
public User findUser(Integer id);
多对一映射
/**
* 用户信息
*/
@Data
public class User {
private int id;
private int age;
private String name;
private String email;
private IdCard idCard;
private int cardId;
private List<Pet> pets;
}
/**
* 宠物
*/
@Data
public class Pet {
private int id;
private String name;
private int userId;
}
<!-- 查询用户 -->
<resultMap id="userResultMap3" type="User">
<!-- 用<id 标记主键会优化性能 -->
<id property="id" column="id"/>
<result property="age" column="age"/>
<result property="name" column="name"/>
<result property="email" column="email"/>
<association property="idCard" column="cardId" select="com.Al_tair.mapper.IdCardMapper.getIdCardById"/>
<collection property="pets" column="id" select="com.Al_tair.mapper.PetMapper.getPetByid"/>
</resultMap>
<select id="getUserAndPetById" parameterType="String" resultMap="userResultMap3">
SELECT * FROM user WHERE user.id = #{id}
</select>
<!-- 查询宠物信息 -->
<select id="getPetByid" parameterType="String" resultType="Pet">
SELECT * FROM pet WHERE pet.user_id = #{id}
</select>
缓存
一级缓存
概念:一级缓存也是本地缓存
目的:通过缓存提高反复查询数据库的数据
测试:通过传入相同、不同的用户id查询相同的用户信息,进行对比发现如下图
一级缓存失效分析
- 当SqlSession 会话关闭了,一级缓存将会失效
- 当执行 SqlSession.clearCache() 可以清除一级缓存
- 当对查询的对象进行了数据库修改,则会导致该对象的缓存失效
二级缓存
二级缓存和一级缓存本质都是为了提高检索效率的技术
配置相关内容
1、配置 cacheEnabled 为true(默认为true)配置在 mybatis-config.xml 文件中
<!-- 全局性关闭或开启所有映射器配置文件中已配置的任何缓存(不会关闭一级缓存) -->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
2、二级缓存可能使用到了序列化技术,需要在实体类上实现序列化接口(Serializable)
3、配置在 xxxMapper.xml 文件中
<!--
配置二级缓存
1、eviction 清除缓存的策略,默认LRU
2、flushInterval 刷新间隔,单位是毫秒
3、size 引用的数量
4、readOnly 只是用来查询,可以提高效率
-->
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
测试用例如下
二级缓存类似全局变量,使用不同 sqlSession 对象依旧能获取到缓存数据
@Test
public void test(){
sqlSession = MyBatisUtils.getSqlSession();
mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.findUserById(2);
if(sqlSession != null){
sqlSession.close();
}
// 使用不同的 sqlSession 对象
sqlSession = MyBatisUtils.getSqlSession();
mapper = sqlSession.getMapper(UserMapper.class);
User user3 = mapper.findUserById(2);
if(sqlSession != null){
sqlSession.close();
}
}
执行顺序
查询数据的顺序: 二级缓存 -> 一级缓存 -> 数据库
二级缓存和一级缓存不会存在相同的数据,因为如果是查询数据库的时候会把数据放到一级缓存里,只有当SqlSession会话结束的时候,才会将一级缓存的数据转移到二级缓存
EnCache 缓存框架
EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认CacheProvider
引入依赖 pom.xml
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.2</version>
</dependency>
<!-- mybatis 支持 ehcache 缓存 -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
<scope>compile</scope>
</dependency>
添加 ehcache.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<!-- 磁盘缓存位置 -->
<diskStore path="java.io.tmpdir/ehcache" />
<!-- 默认缓存 -->
<defaultCache
maxEntriesLocalHeap="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxEntriesLocalDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</defaultCache>
<!-- 自定义缓存 -->
<!--
name: 缓存名称
maxElementsInMemory:缓存最大个数
timeToIdleSeconds: 设置对象在失效前的允许闲置时间(单位:秒)(在一直不访问这个对象的前提下,这个对象可以在cache中的存活时间)
timeToLiveSeconds: 设置对象在失效前允许存活时间(单位:秒)(无论对象访问或是不访问(闲置),这个对象在cache中的最大存活时间)
overflowToDisk: 当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中
diskSpoolBufferSizeMB: 设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区
maxElementsOnDisk: 硬盘最大缓存个数
memoryStoreEvictionPolicy: 当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存(默认策略是最近最少使用的 LRU)
-->
<cache name="UserCache"
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="276000"
overflowToDisk="false"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
添加在 xxxMapper.xml 配置文件
<!-- 启用 Ehcache 缓存 -->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>