MyBatis笔记
标签:
MyBatis
2018年10月28日MingFeng197
简介
MyBatis是什么?
MyBatis是一个Java持久层框架,是apache下的开源项目,前身是ibatis,是一个不完全的ORM框架,mybatis提供输入和输出映射,需要程序员自己写SQL语句,mybatis重点对SQL语句的灵活操作
适用于需求变化频繁,数据模型不固定的项目
JDBC问题总结:
1、数据库连接频繁的创建和关闭,缺点浪费数据库的资源,影响操作效率
设想:使用数据库连接池
2、sql语句是硬编码,如果需求变更需要修改sql,就需要修改java代码,需要重新编译,系统不易维护。
设想:将sql语句 统一配置在文件中,修改sql不需要修改java代码。
3、通过preparedStatement向占位符设置参数,存在硬编码( 参数位置,参数)问题。系统不易维护。
设想:将sql中的占位符及对应的参数类型配置在配置文件中,能够自动输入 映射。
4、遍历查询结果集存在硬编码(列名)。
设想:自动进行sql查询结果向java对象的映射(输出映射)
MyBatis介绍:
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,
并且改名为MyBatis,实质上Mybatis对ibatis进行一些改进。 目前mybatis在github上托管。git(分布式版本控制,当前比较流程)
MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,
而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。
Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatemnt、CallableStatement)配置起来,
并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。
MyBatis的架构
SqlMapConfig.xml(MyBatis全局配置文件,名称不固定)
运行环境(数据源,事务)
mapper.xml(配置SQL语句)...
↓↓↓
SqlSession(会话工厂),作用:创建SqlSession
↓↓↓
SqlSession 面向用户的节后,数据库操作方法 作用:操作数据库; 是线程不安全的;最佳适合场合方法体内
↓↓↓
Executor (数据库操作的执行器): Executor是一个接口,它由两个实现,默认执行器和缓存执行器
↓↓
输入→ MappedStatement MyBatis的封装对象 封装:sql语句(占位符) →输出
↓↓
MySQL
MyBatis的入门程序
第一步:导入jar包:
核心jar包:
mybatis-3.2.7.jar
依赖包:
asm-3.3.1.jar
cglib-2.2.2.jar
commons-logging-1.1.1.jar
javassist-3.17.1-GA.jar
log4j-1.2.17.jar
log4j-api-2.0-rcl.jar
log4j-core-2.0-rcl.jar
slf4j-api-1.7.5.jar
slf4j-log4j12-1.7.5.jar
其它包:
数据库驱动包
添加配置文件:
文件:log4j.properties
# Global logging configuration,建议开发环境中要用debug
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
文件:SqlMapConfig.xml 通过SqlMapConfig.xml加载MyBatis运行环境
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 和spring整合后 environments配置将废除-->
<!-- default:指定默认使用哪个environment 值是要使用environment的id -->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理-->
<transactionManager type="JDBC" />
<!-- 数据库连接池-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" /><!-- 驱动地址 -->
<!-- 数据库的URL -->
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8" />
<property name="username" value="root" />
<property name="password" value="密码" />
</dataSource>
</environment>
</environments>
<!-- 加载mapper.xml -->
<mappers>
<!-- resource:要加载的文件位置 -->
<mapper resource="sqlmap/User.xml" />
</mappers>
</configuration>
第二步:创建实体类:
User.java
public class User{
private int id;
private String username;
private String sex;
private Date birthday;
private String address;
//getter/setter...
}
配置User.xml(重点)
建议命名规则: 表名+mapper.xml
早期ibatis命名规则:表名.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">
<!-- namespace命名空间,为了对sql语句进行隔离,方便管理 ,mapper开发dao方式,使用namespace有特殊作用 -->
<mapper namespace="test">
<!-- 在mapper.xml文件中配置很多的sql语句,执行每个sql语句时,封装为MappedStatement对象
mapper.xml以statement为单位管理sql语句 -->
</mapper>
代码:
创建SqlSessionFactory:
public class MybatisFirst {//这是一个测试类
// 会话工厂 全局变量
private SqlSessionFactory sqlSessionFactory;
// 创建工厂 这里用了JUnit的Before注解来进行加载
@Before
public void init() throws IOException {
String resource = "SqlMapConfig.xml";// 配置文件(SqlMapConfig.xml)
// 加载配置文件到输入 流
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建会话工厂 需要文件的输入流
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
}
根据id查询用户信息
配置文件:
<!-- 根据id查询用户信息 使用select标签 -->
<!--
id:唯一标识 一个statement
#{}:表示 一个占位符,如果#{}中传入简单类型的参数,#{}中的名称随意
parameterType:输入 参数的类型,通过#{}接收parameterType输入 的参数
resultType:输出结果 类型,不管返回是多条还是单条,指定单条记录映射的pojo类型
-->
<select id="findUserById" parameterType="int" resultType="cn.itcast.mybatis.po.User">
SELECT * FROM USER WHERE id= #{id}
</select>
代码:
// 测试:根据id查询用户(得到单条记录)
@Test
public void testFindUserById() {
// 通过sqlSessionFactory创建sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过sqlSession操作数据库
// 第一个参数:statement的位置,等于namespace+statement的id
// 第二个参数:传入的参数
User user = null;
try {
//selectOne方法:用来查询单条记录
user = sqlSession.selectOne("test.findUserById", 2);
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();// 关闭sqlSession
}
System.out.println(user);
}
根据名称模糊查询用户
配置文件:
<!-- 根据用户名称模糊查询用户信息,可能返回多条
${}:表示sql的拼接,通过${}接收参数,将参数的内容不加任何修饰拼接在sql中。
-->
<select id="findUserByName" parameterType="java.lang.String" resultType="cn.itcast.mybatis.po.User">
select * from user where username like '%${value}%'
</select>
代码:
@Test
public void testFindUserByName() {
// 通过sqlSessionFactory创建sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 第一个参数:statement的位置,等于namespace+statement的id
// 第二个参数:传入的参数
List<User> list = null;
try {
//selectList 查找多条记录,返回List
list = sqlSession.selectList("test.findUserByName", "小明");
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();// 关闭sqlSession
}
System.out.println(list.get(0).getUsername());
}
添加一条数据
配置文件:
<!-- 添加用户 使用insert标签
parameterType:输入 参数的类型,User对象 包括 username,birthday,sex,address
#{}接收pojo数据,可以使用OGNL解析出pojo的属性值
#{username}表示从parameterType中获取pojo的属性值
不需要resultType
-->
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User">
<!-- 需求:User对象插入到数据库后,主键要返回设置到User 使代码能拿到新插入数据的ID
思路:通过select LAST_INSERT_ID()语句能够查找到上次插入数据的主键ID
selectKey:用于进行主键返回,定义了获取主键值的sql
order:设置selectKey中sql执行的顺序,相对于insert语句来说
keyProperty:将主键值设置到哪个属性(即pojo类的id)
resultType:select LAST_INSERT_ID()的结果 类型
-->
<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>
<!-- mysql的uuid生成主键 -->
<!-- <insert id="insertUser" parameterType="cn.itcast.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> -->
<!-- oracle没有自增主键机制,我们要使用序列完成主键生成 -->
<!-- oracle 在执行insert之前执行select 序列.nextval() from dual取出序列最大值,将值设置到user对象 的id属性 -->
<!-- <insert id="insertUser" parameterType="cn.itcast.mybatis.po.User">
<selectKey keyProperty="id" order="BEFORE" resultType="int">
select 序列名.nextval() from dual
</selectKey>
INSERT INTO USER(id,username,birthday,sex,address) VALUES(#{id},#{username},#{birthday},#{sex},#{address})
</insert> -->
代码:
@Test
public void testInsertUser() {
SqlSession sqlSession = sqlSessionFactory.openSession();
// 创建插入数据对象
User user = new User();
user.setUsername("测试名称");
user.setAddress("河南郑州");
user.setBirthday(new Date());
user.setSex("1");
try {
sqlSession.insert("test.insertUser", user);//使用insert方法添加数据
sqlSession.commit();// 需要提交事务
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();// 关闭sqlSession
}
System.out.println("用户的id=" + user.getId());
}
根据ID删除用户:
配置:
<!-- 用户删除 使用delete标签 -->
<delete id="deleteUser" parameterType="int">
delete from user where id=#{id}
</delete>
代码:
@Test
public void testDeleteUser() {
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过sqlSession操作数据库
try {
sqlSession.delete("test.deleteUser", 35);//使用delete方法删除
sqlSession.commit();// 需要提交事务
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();// 关闭sqlSession
}
}
根据ID更新用户
配置:
<!-- 用户更新 使用update标签 要求:传入的user对象中包括 id属性值 -->
<update id="updateUser" parameterType="cn.itcast.mybatis.po.User">
update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
</update>
代码:
@Test
public void testUpdateUser() {
SqlSession sqlSession = sqlSessionFactory.openSession();
// 创建更新数据对象,要求必须包括 id
User user = new User();
user.setId(35);
user.setUsername("新数据");
user.setAddress("河南郑州");
user.setBirthday(new Date());
user.setSex("1");
try {
sqlSession.update("test.updateUser", user);//使用update方法更新
sqlSession.commit();// 需要提交事务
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();// 关闭sqlSession
}
System.out.println("用户的id=" + user.getId());
}
MyBatis解决JDBC编程的问题:
1、数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
解决:在SqlMapConfig.xml中配置数据链接池,使用连接池管理数据库链接。
2、Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。
3、向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。
解决:Mybatis自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型。
4、对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
解决:Mybatis自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型。
MyBatis与Hibernate的主要区别:
企业开发进行技术选型 ,考虑mybatis与hibernate适用场景。
mybatis:入门简单,程序容易上手开发,节省开发成本 。
mybatis需要程序员自己编写sql语句,是一个不完全 的ORM框架,对sql修改和优化非常容易实现 。
mybatis适合开发需求变更频繁的系统,比如:互联网项目。
hibernate:入门门槛高,如果用hibernate写出高性能的程序不容易实现。
hibernate不用写sql语句,是一个 ORM框架。
hibernate适合需求固定,对象数据模型稳定,中小型项目,比如:企业OA系统。
总之,企业在技术选型时根据项目实际情况,以降低成本和提高系统 可维护性为出发点进行技术选型
对上面的一些总结:
SqlMapConfig.xml 是mybatis全局配置文件,只有一个,名称不固定的,主要mapper.xml,mapper.xml中配置 sql语句
mapper.xml是以statement为单位进行配置;(把一个sql称为一个statement),
satatement中配置 sql语句、parameterType输入参数类型(完成输入映射),resultType输出结果类型(完成输出映射)。
还提供了parameterMap配置输入参数类型(过期了,不推荐使用了)
还提供resultMap配置输出结果类型(完成输出映射); 讲通过resultMap完成复杂数据类型的映射(一对多,多对多映射)
#{} 表示一个占位符(?),向占位符输入参数;MyBatis会自动进行java类型向jdbc类型的转换
MyBatis在向jdbc类型转换时会自动添加单引号引起来
如果#{}中传入简单类型的参数,#{}中的名称随意
如果#{}接收pojo数据,可以使用OGNL解析出pojo的属性值
${} 表示sql的拼接,通过${}接收的参数,将参数的内容不加任何修饰拼接在sql中
缺点:不能防止SQL注入
${}也可以接收pojo数据,可以使用OGNL解析出pojo的属性值
SqlSession执行增,删,改时需要调用SqlSession.commit();提交事务
配置文件中的SQL语句不能加分号结尾,因为MyBatis解析不了分号,如果添加会报错!
MyBatis开发DAO的两种方法
SqlSession作用范围
是使用局部变量,成员变量?
SqlSessionFactoryBuilder
SqlSessionFactoryBuilder是以工具类方式来使用,
需要创建sqlSessionFactory就new一个SqlSessionFactoryBuilder
SqlSessionFactory
正常开发时,以单例方式管理sqlSessionFactory,整个系统运行过程中sqlSessionFactory只有一个实例,
将来和spring整合后由spring以单例方式管理sqlSessionFactory
SqlSession
sqlSession是一个面向用户(程序员)的接口,程序员调用sqlSession的接口方法进行操作数据库。
sqlSession能否以单例 方式使用??
不能,由于sqlSession是线程不安全,所以sqlSession最佳应用范围在方法体内,
在方法体内定义局部变量使用sqlSession
原始dao方式
程序员需要写dao接口和dao的实现类 与例子类似
- 接口:
public interface UserDao{
public User findUserById(int id) throws Exception;
}
- 实现类:
public class UserDaoImpl implements UserDao {
private SqlSessionFactory sqlSessionFactory;
public UserDaoImpl(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;// 将SqlSessionFactory注入
}
@Override
public User findUserById(int id) throws Exception {
// 创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 根据id查询用户信息
User user = sqlSession.selectOne("test.findUserById", id);
sqlSession.close();
return user;
}
}
- 测试代码:
@Test
public void testFindUserById() throws Exception{
UserDao ud=new UserDaoImpl(sqlSessionFactory);
User user=ud.findUserById(1);
System.out.println(user);
}
mapper代理的方式
程序员需要写dao接口,dao接口实现对象有MyBatis自动生成代理对象.
本身dao在三层架构中就是一个通用的接口
1 dao的实现类中存在重复代码,整个mybatis操作的过程代码模板重复
(先创建sqlsession、调用sqlsession的方法、关闭sqlsession)
2、dao的实现 类中存在硬编码,调用sqlsession方法时将statement的id硬编码。
mapper开发规范:
要想让mybatis自动创建dao接口实现类的代理对象,必须遵循一些规则:
1. mapper.xml中namespace指定为mapper接口的全限定名
例:<mapper namespace="cn.itcast.mybatis.mapper.UserMapper">
此步骤目的:通过mapper.xml和mapper.java进行关联
2. mapper.xml中statement(select标签)的id要与mapper.java中方法名一致
3. mapper.xml中statement的parameterType和mapper.java中方法输入参数类型一致
4. mapper.xml中statement的resultType和mapper.java中方法返回值类型一致
mapper.xml映射文件:
mapper映射文件的命名方式建议:表名+Mapper.xml
例:UserMapper.xml
namespace指定为mapper接口的全限定名
例:
<select id="findUserById" parameterType="int" resultType="cn.itcast.mybatis.po.User">
SELECT * FROM USER WHERE id=#{id}
</select>
mapper接口
MyBatis提出了mapper接口,相当于dao接口
mapper接口的命名方式建议:表名+Mapper
例:
public interface UserMapper{
public User findUserById(int id) throws Exception;
}
将mapper.xml在SqlMapConfig中加载
<mappers>
<mapper resource="路径"/>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
测试方法:
@Test
public void test(){
SqlSession sqlSession=SqlSessionFactory.openSession();
UserMapper um=sqlSession.getMapper(UserMapper.class);
User user=um.findUserById(1);
System.out.println(user);
}
Mapper接口返回集合对象:
不管查询单条还是多条记录,在statement中,resultType的类型都指定为记录映射的pojo类型
mapper接口方法返回值,如果是返回的单个对象,返回值类型是pojo类型,生成的代理对象内部通过selectOne获取记录;
如果返回值类型是集合对象,生成的代理对象内部通过selectList获取记录。
如果方法调用的statement,返回是多条记录,而mapper.java方法的返回值为pojo,
此时代理对象通过selectOne调用,由于返回多条记录,所以报错:
org.apache.ibatis.exceptions.TooManyResultsException:
Expected one result (or null) to be returned by selectOne(), but found: 4
使用mapper代理的方式开发,mapper接口方法输入 参数只有一个,可扩展性是否很差??
可扩展性没有问题,因为dao层就是通用的,可以通过扩展pojo(定义pojo包装类型)将不同的参数(可以是pojo也可以简单类型)传入进去
SqlMapConfig.xml
MyBatis环境加载属性的顺序
注意: MyBatis 将按照下面的顺序来加载属性:
在 properties 元素体内定义的属性首先被读取。
然后会读取properties 元素中resource或 url 加载的属性,它会覆盖已读取的同名属性。
最后读取parameterType传递的属性,它会覆盖已读取的同名属性。
建议使用properties,不要在properties中定义属性,只引用定义的properties文件中属性,
并且properties文件中定义的key要有一些特殊的规则。
SqlMapConfig.xml中配置的内容和顺序
1. properties(属性)
resource:
用来指定(加载)一个properties文件的位置,在之后的设置中可以用${}来获取配置文件中的值;
也可以在properties标签中使用<property/>标签配置属性值
url:
2. settings(全局配置参数)
mybatis运行时可以调整一些全局参数(相当于软件的运行参数),参数的配置参考:mybatis-settings.xlsx
根据使用需求进行参数配置。
注意:小心配置,配置参数会影响mybatis的执行。
ibatis的全局配置参数中包括很多的性能参数(最大线程数,最大等待时间。。。),
通过调整这些性能参数使ibatis达到高性能的运行,mybatis没有这些性能参数,由mybatis自动调节
3. typeAliases(类型别名,常用)
可以将parameterType、resultType中指定的类型 通过别名引用
MyBatis自己提供的一些别名: 它提供了基本类型及其包装类以及Date,BigDecimal的别名;
基本类型前加下划线 如: _int 对应的类型为 int
包装类型前不用加: 如 int 对应的类型为 Integer
date 对应的类型为 Date
decimal 对应的类型为 BigDecimal
bigdecimal 对应的类型为 BigDecimal
例:
<!--定义别名 -->
<typeAliases>
<!-- alias:别名 type:别名映射的类型 -->
<typeAlias type="cn.itcast.mybatis.po.User" alias="user" />
<!-- 批量别名定义: 指定包路径,自动扫描包下的pojo类,定义别名,别名默认为类名(首字母小写或大写) -->
<package name="cn.itcast.mybatis.po"/>
</typeAliases>
<!-- 使用别名 -->
<select id="findUserById" parameter="int" resultType="user">
select * from user where id=#{id}
</select>
4. typeHandlers(类型处理器)
类型处理器将java类型和jdbc类型进行映射。mybatis默认提供很多类型处理器,一般情况下够用了
5. objectFactory(对象工厂)
6. plugins(插件)
7. environments(环境集合属性对象)
environment(环境子属性对象)
transactionManager(事务管理)
dataSource(数据源)
8. mappers(映射器)
配置mapper映射
例:
<!-- 加载mapper映射 如果将和Spring整合后,可以使用整合包中提供的mapper扫描器,下面的mapper就不用配置了 -->
<mappers>
<!-- 通过resource引用mapper的映射文件 -->
<mapper resource="路径" />
<!-- 通过class引用mapper接口 class:配置mapper接口的全限定名; 要求:需要mapper.xml和mapper.java同名且在一个目录中 -->
<mapper class="cn.itcast.mybatis.mapper.UserMapper"/>
<!-- 批量mapper配置 通过package自动扫描包下面的mapper接口 要求:需要mapper.xml和mapper.java同名且在一个目录中 -->
<package name="cn.itcast.mybatis.mapper" />
</mappers>
输入映射类型和输出映射类型
通过parameterType完成输入映射,通过resultType和resultMap完成输出映射
parameterType传递pojo包装对象
可以定义pojo包装类型扩展mapper接口输入参数的内容。
例:需求:自定义查询条件查询用户信息,需要向statement输入查询条件,查询条件可以有user信息、商品信息。。。
包装类型:将来在使用时从页面传入到controller,service,mapper…
public class UserQueryVo{
//用户信息
private User user;
//自定义User的扩展对象
private UserCustom userCustom;
}
扩展类型:
public class UserCustom extends User{
//添加一些扩展...
}
mapper.xml文件:
<select id="findUserList" parameterType="userQueryVo" resultType="user">
select * from user where username like '%${userCustom.username}%'
</select>
mapper.java文件:
public interface UserMapper{
public List<User> findUserByList(UserQueryVo userQueryVo)throws Exception;
}
.异常:
如果parameterType中指定属性错误,异常,找不到getter方法
将来和spring整合后,不是通过调用getter方法来获取属性值,通过反射强读取pojo的属性值
resultType
resultType :指定输出结果的类型(pojo、简单类型、hashmap..),将sql查询结果映射为java对象 。
使用resultType注意:sql查询的列名要和resultType指定pojo的属性名相同,指定相同 属性方可映射成功,
如果sql查询的列名要和resultType指定pojo的属性名全部不相同,list中无法创建pojo对象的。
如果查询记录结果集为一条记录且一列再(可以)使用返回简单类型
输出hashmap:
输出pojo对象可以改用HashMap输出类型,将输出的字段名称座位map的key,value为字段值
resultMap
resultMap:将sql查询结果映射为java对象。
如果sql查询列名和最终要映射的pojo的属性名不一致,使用resultMap将列名和pojo的属性名做一个对应关系(列名和属性名映射配置)
入门:
mapper.xml配置:
<!-- 定义resultMap id:mapper.xml中的唯一标识 type:最终要映射的pojo类型 -->
<resultMap id="userListResultMap" type="user">
<!-- id:要映射结果集的唯一标识(主键) column:结果集的列名 property:指定哪个属性 -->
<id column="查询结果集的主键" property="与之对应的属性名"/>
<!-- result:普通列的映射配置 -->
<result column="查询结果集的列名" property="与之对应的属性名"/>
</resultMap>
使用resultmap:
<!-- 如果resultMap的位置与引用resultMap的定义在同一个文件,直接引用resultMap的id;如果不在一个文件中,要在resultMap的id前加命名空间的名称 -->
<select id="findUserListResultMap" parameterType="userQueryVo" resultMap="userListResultMap">
select id id_,username username_ from user where username like '%${userCustom.username}%'
<select>
mapper接口:
public List<User> findUserListResultMap(UserQueryVo userQueryVo) throws Exception;
动态SQL
mybatis重点是对sql的灵活解析和处理
需求: 将自定义查询条件查询用户列表和查询用户列表总记录数改为动态sql
where和if:
<select id="findUserList" parameterType="userQueryVo" resultType="user">
select * from user
<!-- where标签相当于where关键字,可以自动去除第一个and -->
<where>
<!-- 如果userQueryVo中传入查询条件,再进行sql拼接 -->
<!-- test中userCustome.username表示从userQueryVo读取属性值 -->
<if test="userCustome!=null">
<if test="userCustome.username!=null and userCustome.username!=''">
and username like '%${userCustome.username}%'
</if>
<if test="userCustome.sex!=null and userCustome.sex!=''">
and sex = #{userCustome.sex}
</if>
<!-- ...省略... -->
</if>
</where>
</select>
SQL片段:
通过sql片段可以将通用的sql语句抽取出来,单独定义,在其它的statement中可以引用sql片段。
通用的sql语句,常用:where条件、查询列
SQL片段的定义:
<!-- 将用户查询条件定义为SQL片段 建议对单表的查询调剂淡出抽取SQL片段,提高公用性 注意:不要将where标签放在SQL片段 -->
<sql id="query_user_where">
<!-- 如果userQueryVo中传入查询条件,再进行sql拼接 -->
<!-- test中userCustome.username表示从userQueryVo读取属性值 -->
<if test="userCustome!=null">
<if test="userCustome.username!=null and userCustome.username!=''">
and username like '%${userCustome.username}%'
</if>
<if test="userCustome.sex!=null and userCustome.sex!=''">
and sex = #{userCustome.sex}
</if>
<!-- ...省略... -->
</if>
</sql>
引用SQL片段:
<select id="findUserList" parameterType="userQueryVo" resultType="user">
select * from user
<!-- where标签相当于where关键字,可以自动去除第一个and -->
<where>
<!-- 引用SQL片段,如果SQL片段和引用片段处用不在同一个mapper上 则前面需要加上namespace -->
<include refid="query_user_where"></include>
<!-- <include refid="其它SQL片段"></include> -->
</where>
</select>
foreach
在statement通过foreach遍历parameterType中的集合类型。
需求:根据多个用户id查询用户信息
在UserQueryVo中定义List ids
在userQueryvo中定义list ids存储多个id
public class UserQueryVo{
//用户信息
private User user;
//自定义user的扩展对象
private UserCustome userCustom;
//用户id集合
private List<Integer> ids;
}
修改where语句:
使用foreach遍历list:
<sql id="query_user_where">
<!-- 如果userQueryVo中传入查询条件,再进行sql拼接 -->
<!-- test中userCustome.username表示从userQueryVo读取属性值 -->
<if test="userCustome!=null">
<if test="userCustome.ids!=null">
<!-- 要生成的SQL语句:select * from user where id in (1,3,...,n) -->
<!-- collection:集合的属性 open:开始循环拼接的串 close:结束循环拼接的串 item:每次循环获取到的对象 separator:每两次循环中间拼接的串 -->
<foreach collection="ids" open=" and id in (" close=")" item="id" separator=",">
#{id}
</foreach>
</if>
<!-- ...省略... -->
</if>
</sql>
高级映射查询(一对一,一对多,多对多)
商品订单的数据模型:
学会在企业中如何去分析陌生表的数据模型:
1、学习单表记录了什么东西(去学习理解需求)
2、学习单表重要字段的意义(优先学习不能为空的字段)
3、学习表与表之间的关系(一对一、一对多、多对多)
通过表的外键分析表之间的关系
注意:分析表与表之间的关系时是要建立在具体 的业务意义基础之上
数据库表:
用户表user:记录了购买商品的用户
订单表orders:记录了用户所创建的订单信息
订单明细表orderdetail:记录了用户创建订单的详细信息
商品信息表items:记录了商家提供的商品信息
分析表与表之间的关系:
用户user和订单orders:
user---->orders:一个用户可以创建多个订单 一对多
orders-->user:一个订单只能由一个用户创建 一对一
订单orders和订单明细orderdetail:
orders-->orderdetail:一个订单可以包括多个订单明细 一对多
orderdetail-->orders:一个订单明细只属于一个订单 一对一
订单明细orderdetail和商品信息items:
orderdetail-->items:一个订单明细对应一个商品信息 一对一
items--> orderdetail:一个商品对应多个订单明细 一对多
一对一查询
需求:查询订单信息关联查询用户信息
SQL语句:
查询语句:
先确定主查询表:订单信息表
再确定关联查询表:用户信息
通过orders关联查询用户使用user_id一个外键,只能关联查询出一条用户记录就可以使用内连接
SELECT orders.*,user.username,user.sex FROM orders,USER WHERE orders.user_id = user.id
使用resultType实现
-
创建PO类:
-
一对一查询映射的POJO
创建pojo包括 订单信息和用户信息,resultType才可以完成映射。
创建OrderCustom作为自定义pojo(订单扩展对象,用于订单和用户的查询结果映射),继承sql查询列多的po类。
public class OrderCustom extends Orders{
private String username;//补充用户信息
private String sex;
private String address;
}
Mapper.xml
<select id="findOrderUserList" resultType="orderCustom">
SELECT orders.*,user.username,user.sex FROM orders,USER WHERE orders.user_id = user.id
</select>
Mapper.java
public interface OrderMapperCustom{
public List<OrderCustom> findOrderUserList() throws Exception;
}
使用resultMap实现
resultMap提供一对一关联查询的映射和一对多关联查询映射,
一对一映射思路:将关联查询的信息映射到pojo中,如下:
在Orders类中创建一个User属性,将关联查询的信息映射到User属性中
public class Orders{
private Integer id;
private Integer userId;
private String number;
private Date createtime;
//关联用户信息
private User user;
}
mapper.xml
<!-- 一对一查询 使用resultMap完成 查询订单关联查询用户信息 -->
<select id="findOrderUserListResultMap" resultMap="ordersUserResultMap">
select orders.*,user.username,user.sex from orders,user where orders.user_id=user.id
</select>
<!-- resultMap定义: -->
<resultMap type="orders" id="ordersUserResultMap">
<!-- 完成了订单信息的映射配置,column是整个记录的id(表id) property是映射到type(orders)的属性名 -->
<id column="id" property="id"/>
<result column="user_id" property="userId" />
<!-- 其它字段配置省略... -->
<!-- 下面完成关联信息的映射: association:用于对关联信息映射到的单个pojo property:要将关联信息映射到orders的哪个属性中 javaType:关联信息映射到orders的属性的类型,是user的类型 -->
<association property="user" javaType="user">
<id column="user_id" property="id"/>
<!-- result就是普通属性的映射 -->
<result colum"username" property="username"/>
<!-- 其它字段配置省略... -->
</association>
</resultMap>
mapper.java
public List<Orders> findOrderUserListResultMap() throws Exception;
第二种方法:
<!-- 一对一查询 使用resultMap完成 查询订单关联查询用户信息 -->
<select id="findOrderUserListResultMap" resultMap="ordersUserResultMap">
select orders.*,user.username,user.sex from orders,user where orders.user_id=user.id
</select>
<!-- resultMap定义: -->
<resultMap type="orders" id="ordersUserResultMap">
<!-- 完成了订单信息的映射配置,column是整个记录的id(表id) property是映射到type(orders)的属性名 -->
<id column="id" property="id"/>
<result column="user_id" property="userId" />
<!-- 其它字段配置省略... -->
<!-- 使用子查询方法 column:将它作为参数传入调用的select中 -->
<association property="user" javaType="user" column="user_id" select="selId">
</association>
</resultMap>
<select id="selId" parameterType="int" resultType="User">
select user from user where id=#{id}
</select>
小结:
resultType:要自定义pojo 保证sql查询列和pojo的属性对应,这种方法相对较简单,所以应用广泛。
resultMap:使用association完成一对一映射需要配置一个resultMap,过程有点复杂,如果要实现延迟加载就只能用resultMap实现 ,
如果为了方便对关联信息进行解析,也可以用association将关联信息映射到pojo中方便解析。
一对多查询
一对多简单:
需求:要查询订单信息及订单下的订单明细信息
SQL语句:
select orders.*,user.username,user.sex,orderdetail.id orderdetail_id,orderdetail.items_num,orderdetail.items_id from orders,user,orderdetail where orders.user_id = user.id and orders.id = orderdetail.orders_id
resultMap进行一对多映射思路:
resultMap提供<collection>
标签完成关联信息映射到集合对象中
在Orders类中创建集合属性:
public class Orders{
private Integer id;
private Integer userId;
private String number;
private Date createtime;
private String note;
private User user;//关联用户信息
private List<Orderdetail> orderdetails;//订单明细
}
mapper.xml
<!-- 一对多查询 使用resultMap完成 查询订单关联查询订单明细 -->
<select id="findOrderAndOrderDetails" resultMap="orderAndOrderDetails">
select orders.*,user.username,user.sex,orderdetail.id orderdetail_id,orderdetail.items_num,orderdetail.items_id from orders,user,orderdetail where orders.user_id = user.id and orders.id = orderdetail.orders_id
</select>
<!-- resultMap定义 -->
<resultMap type="orders" id="orderAndOrderDetails" extends="ordersUserResultMap">
<!-- 映射订单信息和用户信息,这里使用继承ordersUserResultMap 继承后该标签拥有父标签的所有配置信息 -->
<!-- 订单明细信息: property:要将关联信息映射到orders的哪个属性中 ofType:集合中pojo类型 -->
<collection property="orderdetails" ofType="orderdetail">
<!-- id:关联信息订单明细的唯一标识 property:Orderdetail的属性名 -->
<id column="orderdetail_id" property="id" />
<result column="items_num" property="itemsNum">
<!-- ...省略其它属性配置... -->
</coolection>
</resultMap>
mapper.java
//一对多查询,使用resultMap
public List<Orders> findOrderAndOrderDetails() throws Exception;
一对多复杂:
需求:查询所有用户信息,关联查询订单及订单明细信息及商品信息,订单明细信息中关联查询商品信息
SQL语句:
select orders.*,user.username,user.sex ,orderdetail.id orderdetail_id,orderdetail.items_num,orderdetail.items_id,items.name items_name,items.detail items_detail FROM orders,USER,orderdetail,items WHERE orders.user_id = user.id AND orders.id = orderdetail.orders_id AND items.id = orderdetail.items_id
POJO定义:
在User中创建映射的属性:集合List<Orders> orderlist;
在Orders中创建映射的属性: 集合 List<Orderdetail> orderdetails
在Orderdetail中创建商品属性: POJO Items items;
mapper.xml
<!-- 配置SQL语句,这里省略了 -->
<select>...</select>
<!-- resultMap定义 -->
<resultMap>
<id column="user_id" property="id"/>
<result column="username" property="username"/>
<!-- ...其它属性配置省略... -->
<!-- 订单信息 -->
<collection property="orderlist" ofType="orders">
<id column="id" property="id"/>
<result column="user_id" property="userId" />
<!-- ...其它属性配置省略... -->
<!-- 订单明细映射 -->
<collection property="orderdetails" ofType="orderdetail">
<!-- id:关联信息订单明细的唯一标识 property:Orderdetail的属性名 -->
<id column="orderdetail_id" property="id" />
<result column="items_num" property="itemsNum" />
<!-- ...其它属性配置省略... -->
<!-- 商品信息 -->
<association property="items" javaType="items">
<id column="item_id" property="id" />
<!-- ...其它属性配置省略... -->
</association>
</collection>
</collection>
</resultMap>
mapper.java
public List<User> findUserOrderDetail() throws Exception;
多对多查询
一对多是多对多的特例; 不做明细,请参考一对多
延迟加载
使用延迟加载的意义:
在进行数据查询时,为了 提高数据库查询性能 ,尽量使用单表查询,因为单表查询比多表关联查询速度要快。
如果查询单表就可以满足需求,一开始先查询单表,当需要关联信息时,再关联查询,当需要关联信息再查询这个叫延迟加载。
mybatis中resultMap
提供延迟加载功能,通过resultMap配置延迟加载。
配置MyBatis支持延迟加载:
设置项 | 描述 | 允许值 | 默认值 |
---|---|---|---|
lazyLoadingEnabled | 全局性设置懒加载,如果设为false,则所有相关联的都会被初始化加载 | true | false | false |
aggressiveLazyLoading | 当设置为true的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载 | true | false | true |
<!-- 全局配置参数 在SqlMapConfig总文件中配置 -->
<settings>
<!-- 延迟加载总开关 -->
<setting name="lazyLoadingEnabled" value="true">
<!-- 设置按需加载 -->
<setting name="aggressiveLazyLoading" value="false">
</settings>
延迟加载实现:
一对一延迟加载
实现思路:
需求:
查询订单及用户的信息,一对一查询。
刚开始只查询订单信息
当需要用户时调用 Orders类中的getUser()方法执行延迟加载 ,向数据库发出sql
mapper.xml
<select id="findOrderUserListLazyLoading" resultMap="orderCustomLazyLoading">
select orders.* from orders
</select>
<!-- resultMap配置 一对一查询延迟加载 -->
<resultMap type="orders" id="orderCustomLazyLoading">
<!-- 完成了订单信息的映射配置 -->
<id column="id" property="id" />
<result column="user_id" property="userId">
<!-- ...其它属性配置省略... -->
<!-- 配置用户信息的延迟加载 select:延迟加载执行的sql所在的statement的id,如果不在同一个namespace需要加namespace;
sql: 根据用户id查询用户信息 column: 关联查询的列
property:将关联查询的用户信息设置到Orders的哪个属性-->
<association property="user" select="cn.itcast.mybatis.mapper.UserMapper.findUserById" column="user_id">
</association>
</resultMap>
mapper.java
public List<Orders> findOrderUserListLazyLoading() throws Exception;
测试代码:
@Test
public void test() throws Exception{
SqlSession sqlSession=sqlSessionFactory.openSession();
//创建mapper代理对象
OrdersMapperCustom omc=sqlSession.getMapper(OrdersMapperCustom.class);
//调用方法
List<Orders> list=omc.findOrderUserListLazyLoading();
//这里执行延迟加载,要发出SQL
User user=list.get(0).getUser();
System.out.println(user);
}
一对多延迟加载: 一对多延迟加载的方法同一对一延迟加载,在collection标签中配置select内容
一对一中是对association
标签添加select
属性;一对多是在collection
标签中添加 select
属性
resultType,resultMap延迟加载使用场景总结
延迟加载:
延迟加载实现的方法多种多样,在只查询单表就可以满足需求,
为了提高数据库查询性能使用延迟加载,再查询关联信息。
mybatis提供延迟加载的功能用于service层。
resultType:
作用:
将查询结果按照sql列名pojo属性名一致性映射到pojo中。
场合:
常见一些明细记录的展示,将关联查询信息全部展示在页面时,
此时可直接使用resultType将每一条记录映射到pojo中,
在前端页面遍历list(list中是pojo)即可。
resultMap:
使用association和collection完成一对一和一对多高级映射。
association:
作用:
将关联查询信息映射到一个pojo类中。
场合:
为了方便获取关联信息可以使用association将关联订单映射为pojo,比如:查询订单及关联用户信息。
collection:
作用:
将关联查询信息映射到一个list集合中。
场合:
为了方便获取关联信息可以使用collection将关联信息映射到list集合中,比如:查询用户权限范围模块和功能,可使用collection将模块和功能列表映射到list中。
查询缓存
缓存存在的意义: 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
MyBatis持久层缓存: mybatis一级缓存是一个SqlSession级别,sqlsession只能访问自己的一级缓存的数据,二级缓存是跨sqlSession,是mapper级别的缓存,对于mapper级别的缓存不同的sqlsession是可以共享的
一级缓存
原理:
第一次发出一个查询sql,sql查询结果写入sqlsession的一级缓存中,
缓存使用的数据结构是一个map<key,value>
key:hashcode+sql+sql输入参数+输出参数(sql的唯一标识)
value:用户信息, 同一个sqlsession再次发出相同的sql, 就从缓存中取不走数据库。
如果sqlSession出现commit操作(修改、添加、删除),本sqlsession中的一级缓存区域全部清空,
下次再去缓存中查询不到所以要从数据库查询,从数据库查询到再写入缓存。
//每次查询都先从缓存中查询;如果缓存中查询到则将缓存数据直接返回。
list = resultHandler == null ? (List<E>)localCache.getObject(key) : null;
//如果缓存中查询不到就从数据库查询:
list=queryFromDatabase(ms,parameter,rowBounds,resultHandler,key,boundSql);
一级缓存配置
MyBatis默认支持一级缓存,不需要配置
注意:mybatis和spring整合后进行mapper代理开发,不支持一级缓存,mybatis和spring整合,spring按照mapper的模板去生成mapper代理对象,模板中在最后统一关闭sqlsession。
一级缓存测试
//一级缓存
@Test
public void testCache()throws Exception{
SqlSession sqlSession=sqlSessionFactory.openSession();
UserMapper um=sqlSession.getMapper(UserMapper.class);
//第一次查询用户id为1的用户
User user=um.findUserById(1);
System.out.println(user);
//修改用户会清空缓存,目的防止查询出脏数据
/*user.setUsername("测试");
um.updateUser(user);
sqlSession.commit();*/
//第二次查询用户id为1的用户
User user2=um.findUserById(1);
System.out.println(user2);
sqlSession.close();
}
二级缓存
原理:
二级缓存的范围是mapper级别(mapper同一个命名空间),
mapper以命名空间为单位创建缓存数据结构,结构是map<key、value>。
//每次查询先看是否开启二级缓存,如果开启从二级缓存的数据结构中取缓存数据,
//如果从二级缓存没有取到,再从一级缓存中找,如果一级缓存也没有,从数据库查询。
List<E> list = (List<E>) tcm.getObject(cache,key);
MyBatis二级缓存配置
<!-- 在核心配置文件SqlMapConfig.xml中加入配置开启二级缓存总开关 -->
<setting name="cacheEnabled" value="true">
开启二级缓存总开关后还要在你的Mapper映射文件中添加一行: <cache />
,表示此mapper开启二级缓存
查询结果映射的POJO序列化
mybatis二级缓存需要将查询结果映射的pojo实现 java.io.serializable接口,如果不实现则抛出异常:
org.apache.ibatis.cache.CacheException: Error serializing object. Cause: java.io.NotSerializableException: cn.itcast.mybatis.po.User
二级缓存可以将内存的数据写到磁盘,存在对象的序列化和反序列化,所以要实现java.io.serializable
接口。
如果结果映射的pojo中还包括了pojo,都要实现java.io.serializable
接口。
二级缓存禁用
对于变化频率较高的sql,需要禁用二级缓存:
在statement中设置useCache=false可以禁用当前select语句的二级缓存,
即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。
<select id="findOrderListResultMap" resultMap="ordersUserMap" useCache="false">
刷新缓存
如果sqlsession操作commit操作,对二级缓存进行刷新(全局清空)。
设置statement的flushCache是否刷新缓存,默认值是true。
测试代码
public void testCache() throws Exception {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserMapper um1 = sqlSession1.getMapper(UserMapper.class);
UserMapper um2 = sqlSession2.getMapper(UserMapper.class);
UserMapper um3 = sqlSession3.getMapper(UserMapper.class);
//第一次查询用户id为1的用户
User user=um1.findUserById(1);
System.out.println(user);
sqlSession1.close();
//中间修改用户要清空缓存,目的防止查询出脏数据
user.setUsername("测试1");
um3.updateUser(user);
sqlSession3.commit();
sqlSession3.close();
//第二次查询用户id为1的用户
User user2=um2.findUserById(1);
System.out.println(user2);
sqlSession2.close();
}
MyBatis的cache参数
mybatis的cache参数只适用于mybatis维护缓存
flushInterval(刷新间隔)
可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。
默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
size(引用数目)
可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是1024。
readOnly(只读)
属性可以被设置为true或false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。
如下例子:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,
而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会导致冲突。可用的收回策略有, 默认的是 LRU:
1. LRU – 最近最少使用的:移除最长时间不被使用的对象。
2. FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
3. SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
4. WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象
MyBatis和ehcache缓存框架整合
mybatis二级缓存通过ehcache维护缓存数据
MyBatis和ehcache思路
通过mybatis和ehcache框架进行整合,就可以把缓存数据的管理托管给ehcache。
在mybatis中提供一个cache接口,只要实现cache接口就可以把缓存数据灵活的管理起来。
public interface Cache{
String getId();
void putObject(Object key,Object value);
Object getObject(Object key);
}
MyBatis中默认实现类:
public class PerpetualCache implements Cache {}
下载和ehcache整合的jar包
ehcache-core-2.6.5.jar
mybatis-ehcache-1.0.2.jar
ehcache对cache接口的实现类:
public final class EhcacheCache implements Cache {}
配置ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!-- diskStore: 缓存数据持久化的目录 地址 -->
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
属性说明:
diskStore: 指定数据在磁盘中的存储位置
defaultCache: 当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defaultCache/>指定的管理策略
以下属性是必须的:
maxElementsInMemory : 在内存中缓存的Element的最大数目
maxElementsOutDisk : 在磁盘上缓存的Element的最大数目,若是0表示无穷大
eternal: 设定缓存的Elements是否永远不过期;如果为true,则表示始终有效,如果false,那么还要根据timeToIdleSeconds和timeToLiveSeconds判断
overflowToDisk: 设定当内存缓存溢出的时候是否将过期的Element缓存到硬盘上
以下属性是可选的:
timeToIdleSeconds: 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性值时,这些数据便会删除;
默认值是0,也就是可闲置时间无穷大
timeToLiveSeconds: 缓存Element的有效生命期,默认值是0,也就是存活时间无穷大
diskSpoolBufferSizeMB: 这个参数设置DiskStore(磁盘缓存)的缓存区大小,默认是30MB,每个Cache都应该有自己的一个缓冲区
diskPersistent 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false;
diskExpiryThreadIntervalSeconds: 磁盘缓存的清理线程运行间隔,默认是120秒;每隔120秒,相应的线程会进行一次EhCache中数据的清理工作
memoryStoreEvictionPolicy: 当内存缓存达到最大,有新的Element加入的时候,移出缓存中Element的策略;默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)
整合测试
在mapper.xml添加ehcache配置:
<!-- 单位:毫秒 如果不配置type属性 将默认使用MyBatis缓存 -->
<cache type="org.mybatis.caches.ehcache.EhcacheCache">
<property name="timeToIdleSeconds" value="12000">
<property name="timeToLiveSeconds" value="3600">
<!-- 同ehcache参数maxElementsInMemory -->
<property name="maxEntriesLocalHeap" value="1000">
<!-- 同ehcache参数maxElementsOutDisk -->
<property name="maxEntriesLocalDisk" value="10000000">
<property name="memoryStoreEvictionPolicy" value="LRU">
</cache>
二级缓存的应用场景
对查询频率高,变化频率低的数据建议使用二级缓存。
对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis二级缓存技术
降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等。
实现方法如下:通过设置刷新间隔时间,由mybatis每隔一段时间自动清空缓存,
根据数据变化频率设置缓存刷新间隔flushInterval,比如设置为30分钟、60分钟、24小时等,根据需求而定。
MyBatis局限性
mybatis二级缓存对细粒度的数据级别的缓存实现不好;
比如如下需求:对商品信息进行缓存:
由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,
此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,
因为mybaits的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。
解决此类问题需要在业务层根据需求对数据有针对性缓存。
MyBatis和Spring整合
整合思路:
1. 让Spring管理SqlSessionFactory
2. 让Spring管理mapper对象和dao
使用Spring和MyBatis整合开发mapper代理及原始dao接口
自动开启事务,自动关闭SqlSession
3. 让Spring管理数据源(数据库连接池)
第一步:加入jar包和配置文件
MyBatis本身的jar包
数据库驱动包
Spring的jar包以及Spring和MyBatis整合包(从MyBatis的官方下载整合包)
mybatis-spring-1.2.2.jar
log4j.properties
SqlMapConfig.xml
mybatis配置文件:别名,settings,数据源不在这里配置
applicationContext.xml
1. 数据源(dbcp连接池)
2. SqlSessionFactory
3. mapper或者dao(bean)
整合开发原始dao接口
在applicationContext.xml配置SqlSessionFactory:
<!-- 在applicationContext中配置SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- MyBatis配置文件 -->
<property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml">
</bean>
开发DAO:
public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {
@Override
public User findUserById(int id) throws Exception {
//创建SqlSession
SqlSession sqlSession = this.getSqlSession();
//根据id查询用户信息
User user=sqlSession.selectOne("test.findUserById",id);
return user;
}
}
配置DAO:
<!-- 在applicationContext中配置dao -->
<bean id="userDao" class="cn.itcast.mybatis.dao.UserDaoImpl">
<property name="sqlSessionFactory" ref="SqlSessionFactory"/>
</bean>
配置Mapper.xml
<!-- 在SqlMapConfig.xml 中配置 -->
<configuration>
<!-- 定义别名 -->
<typeAliases>
<!-- 批量定义别名 指定包路径 自动扫描pojo -->
<package name="cn.itcast.mybatis.po"/>
</typeAliases>
<mappers>
<!-- 原始dao使用user.xml -->
<mapper resource="User.xml"/>
<package name="cn.itcast.mybatis.mapper"/>
</mappers>
</configuration>
测试DAO接口:
@Test
public void test() throws Exception {
//从Spring容器中获取UserDao这个bean
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
User user=userDao.findUserById(1);
System.out.println(user);
}
整合开发mapper代理方法
- 开发mapper.xml和mapper.java
- 使用MapperFactoryBean
<!-- 配置mapper MapperFactoryBean:用于生成mapper代理对象 -->
<!-- 这个方法对于每个mapper都需要配置,比较繁琐 -->
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="cn.itcast.mybatis.mapper.UserMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
- 使用MapperScannerConfigurer(扫描mapper)
<!-- MapperScannerConfigurer: mapper的扫描器,将包下的mapper接口自动创建代理对象
自动创建到spring容器中,bean的id是mapper的类名(首字母小写) -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 配置扫描包的路径,如果要扫描多个包,中间使用半角逗号分隔 -->
<property name="basePackage" value="cn.itcast.mybatis.mapper"/>
<!-- 使用sqlSessionFactoryBeanName -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
- 测试mapper接口
@Test
public void test() throws Exception {
UserMapper userMapper=(UserMapper)applicationContext.getBean("userMapper");
User user=userMapper.findUserById(1);
System.out.println(user);
}
MyBatis逆向工程
什么是MyBatis的逆向工程
MyBatis官方为了提高开发效率,提高对自动单表生成SQL,包括mapper.xml,mapper.java,表名.java(po类)
根据MyBatis官网提供的 mybatis-generator-core-1.3.2
在企业开发中通常是在设计阶段对表进行设计 、创建。
在开发阶段根据表结构创建对应的po类。
mybatis逆向工程的方向:由数据库表 ----> java代码
逆向工程 使用配置
所需要的jar包:
mybatis-generator-core-1.3.2.jar
数据库驱动包
第一步:配置generatorConfig.xml
需要使用配置的地方:
- 连接数据库的地址和驱动
<!-- 数据库连接的信息:数据库驱动类,连接地址,用户名,密码 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis"
userId="root" password="mysql">
</jdbcConnection>
- 需要配置po类的包路径
<!-- targetProject: 生成PO类的位置 -->
<javaModelGenerator targetPackage="cn.itcast.mybatis.po"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false"/>
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true"/>
</javaModelGenerator>
- 需要配置mapper的包路径
<!-- targetProject: mapper映射文件生成的位置 -->
<sqlMapGenerator targetPackages="cn.itcast.mybatis.mapper"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false"/>
</sqlMapGenerator>
<!-- targetPackages: mapper接口生成的位置 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="cn.itcast.mybatis.mapper"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false"/>
</javaClientGenerator>
- 指定数据表
<!-- 指定数据表 -->
<table tableName="items"></table>
<table tableName="orders"></table>
<table tableName="orderdetail"></table>
第二部:配置执行java程序
- java程序
public void generator() throws Exception{
//...代码省略...
//指定逆向工程的配置文件
File configFile = new File("generatorConfig.xml");
//...代码省略...
}
第三步:将生成的代码拷贝到工程中
第四步:测试生成代码
import static org.junit.Assert.*;
public class ItemsMapperTest{
private ApplicationContext applicationContext;
private ItemsMapper itemsMapper;
@Before
public void setUp() throws Exception {
applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
itemsMapper = (ItemsMapper)applicationContext.getBean("itemsMapper");
}
@Test//根据主键查询
public void testSelectByPrimaryKey{
Items items=itemsMapper.selectByPrimaryKey(1);
System.out.println(items);
}
@Test//自定义条件查询
public void testSelectByExample(){
ItemsExample itemsExample=new ItemsExample();
//Criteria是ItemsExample的内部类
ItemsExample.Criteria criteria= itemsExample.createCriteria();
//criteria.andNameEqualTo("测试");//添加查询条件
criteria.andNameLike("%测试%");//模糊查询 不自动加百分号
List<Items> list=itemsMapper.selectByExample(itemsExample);
//这个方法会将大文本(text)字段也查询出来
//itemsMapper.selectByExampleWithBLOBS(itemsExample);
}
@Test//根据主键更新
public void testUpdateByPrimaryKey(){
//将更新的内容全部更新到数据库
//updateByPrimaryKey通常是先查询再设置更新的值
itemsMapper.updateByPrimaryKey();
//如果更新对象的字段不为空,才更新到数据库
//常用于指定字段的更新,不用先查询出来,new出来的对象必须设置主键
//itemsMapper.updateByPrimaryKeySelective();
//自定义条件更新
//itemsMapper.updateByExample(record,example);
}
}
MyBatis笔记 2018年10月28日
MingFeng197