MyBatis介绍
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis,实质上Mybatis对ibatis进行一些改进。
MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。
Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatemnt、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。
1.1 Mybatis架构
1、 mybatis配置
SqlMapConfig.xml,此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息。
mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml中加载。
2、 通过mybatis环境等配置信息构造SqlSessionFactory即会话工厂
3、 由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行。
4、 mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。
5、 Mapped Statement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id。
6、 Mapped Statement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。
7、 Mapped Statement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。
1.2mybatis jar包
所需依赖
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.4</version> </dependency>
1.3 Mybatis入门程序
1.3.1 第一步:创建java工程
使用eclipse创建java工程,jdk使用1.7.0_72。
1.3.2 第二步:加入jar包
加入mybatis核心包、依赖包、数据驱动包。
1.3.3 第三步:log4j.properties 和 db.properties
log4j.properties
# Global logging configuration
# 在实际开发过程中,日志级别要设置成DEBUG,生产环境要设置成info或error
log4j.rootLogger=debug, stdout
# MyBatis logging configuration...
log4j.logger.org.mybatis.example.BlogMapper=TRACE
# 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
mybatis默认使用log4j作为输出日志信息。
db.properties
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/数据库名 jdbc.username=root jdbc.password =****
1.3.4第四步:SqlMapConfig.xml
在classpath下创建SqlMapConfig.xml,如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <properties resource="db.properties"></properties> <!--开启延迟加载放到properties 标签下--> <settings> <setting name="lazyLoadTriggerMethods" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> <setting name="cacheEnabled" value="true"/> </settings> <!-- SSM SSH Spring MyBatis Hibernate Struts --> <!-- 以后该配置将会配置到Spring内,此处被废弃 --> <typeAliases> <package name="com.hxzy.myBatis_ehcache"/> <!--<typeAlias type="com.hxzy.mybatis.pojo.Users" alias="users"/>--> <!--<typeAlias type="com.hxzy.mybatis.pojo.Orders" alias="orders"/>--> <!--<typeAlias type="com.hxzy.mybatis.pojo.custom.UsersQueryVo" alias="usersQueryVo"/>--> <!--<typeAlias type="com.hxzy.mybatis.pojo.custom.UsersCustom" alias="usersCustom"/>--> </typeAliases> <!-- 和spring整合后 environments配置将废除--> <environments default="developer"> <environment id="developer"> <!-- MyBatis的事物的控制 --> <transactionManager type="JDBC"></transactionManager> <!-- MyBatis的数据源 --> <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接口--> <mappers> <!--<mapper resource="sqlmap/UserMapper.xml" />--> <package name="com.hxzy.myBatis_ehcache.mapper"/> </mappers> </configuration>
settings(配置)
SqlMapConfig.xml中配置的内容和顺序如下:
properties(属性)
settings(全局配置参数)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境集合属性对象)
environment(环境子属性对象)
transactionManager(事务管理)
dataSource(数据源)
mappers(映射器)
自定义别名:
<typeAliases> <!-- 单个别名定义 --> <typeAlias alias="user" type="cn.atwyl.mybatis.po.User"/> <!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以) --> <package name="cn.atwyl.mybatis.po"/> <package name="其它包"/> </typeAliases>
typeHandlers(类型处理器)
<select id="findUserById" parameterType="int" resultType="user"> select * from user where id = #{id} </select>
类型处理器 |
Java类型 |
JDBC类型 |
BooleanTypeHandler |
Boolean,boolean |
任何兼容的布尔值 |
ByteTypeHandler |
Byte,byte |
任何兼容的数字或字节类型 |
ShortTypeHandler |
Short,short |
任何兼容的数字或短整型 |
IntegerTypeHandler |
Integer,int |
任何兼容的数字和整型 |
LongTypeHandler |
Long,long |
任何兼容的数字或长整型 |
FloatTypeHandler |
Float,float |
任何兼容的数字或单精度浮点型 |
DoubleTypeHandler |
Double,double |
任何兼容的数字或双精度浮点型 |
BigDecimalTypeHandler |
BigDecimal |
任何兼容的数字或十进制小数类型 |
StringTypeHandler |
String |
CHAR和VARCHAR类型 |
ClobTypeHandler |
String |
CLOB和LONGVARCHAR类型 |
NStringTypeHandler |
String |
NVARCHAR和NCHAR类型 |
NClobTypeHandler |
String |
NCLOB类型 |
ByteArrayTypeHandler |
byte[] |
任何兼容的字节流类型 |
BlobTypeHandler |
byte[] |
BLOB和LONGVARBINARY类型 |
DateTypeHandler |
Date(java.util) |
TIMESTAMP类型 |
DateOnlyTypeHandler |
Date(java.util) |
DATE类型 |
TimeOnlyTypeHandler |
Date(java.util) |
TIME类型 |
SqlTimestampTypeHandler |
Timestamp(java.sql) |
TIMESTAMP类型 |
SqlDateTypeHandler |
Date(java.sql) |
DATE类型 |
SqlTimeTypeHandler |
Time(java.sql) |
TIME类型 |
ObjectTypeHandler |
任意 |
其他或未指定类型 |
EnumTypeHandler |
Enumeration类型 |
VARCHAR-任何兼容的字符串类型,作为代码存储(而不是索引)。 |
mappers(映射器)
Mapper配置的几种方法:
<mapper resource=" " />
使用相对于类路径的资源
如:<mapper resource="sqlmap/User.xml" />
<mapper url=" " />
使用完全限定路径
如:<mapper url="file:///D:workspace_spingmvcmybatis_01configsqlmapUser.xml" />
<mapper class=" " />
使用mapper接口类路径
如:<mapper class="cn.atwyl.mybatis.mapper.UserMapper"/>
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
<package name=""/>
注册指定包下的所有mapper接口
如:<package name="cn.atwyl.mybatis.mapper"/>
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
1.3.5 第五步:po类
1.3.6 第六步:程序编写
映射文件:
在classpath下创建映射文件UserMapper.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="test"> </mapper>
namespace :命名空间 Mapper接口的路径
<!-- 根据id获取用户信息 --> <select id="findUserById" parameterType="int" resultType="cn.atwyl.mybatis.po.User"> select * from user where id = #{id} </select> <!-- 自定义条件查询用户列表 --> <select id="findUserByUsername" parameterType="java.lang.String" resultType="cn.atwyl.mybatis.po.User"> select * from user where username like '%${value}%' </select>
parameterType:定义输入到sql中的映射类型,#{id}表示使用preparedstatement设置占位符号并将输入变量id传到sql。利用#可以防止Sql注入
resultType:定义结果映射类型。
加载映射文件
mybatis框架需要加载映射文件
<mappers> <mapper resource="Mapper配置文件的路径"/> </mappers>
测试程序:
public class Mybatis_first { //会话工厂 private SqlSessionFactory sqlSessionFactory; @Before public void createSqlSessionFactory() throws IOException { // 配置文件 String resource = "SqlMapConfig.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); // 使用SqlSessionFactoryBuilder从xml配置文件中创建SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder() .build(inputStream); } // 根据 id查询用户信息 @Test public void testFindUserById() { // 数据库会话实例 SqlSession sqlSession = null; try { // 创建数据库会话实例sqlSession sqlSession = sqlSessionFactory.openSession(); // 查询单个记录,根据用户id查询用户信息 User user = sqlSession.selectOne("test.findUserById", 10); // 输出用户信息 System.out.println(user); } catch (Exception e) { e.printStackTrace(); } finally { if (sqlSession != null) { sqlSession.close(); } } } // 根据用户名称模糊查询用户信息 @Test public void testFindUserByUsername() { // 数据库会话实例 SqlSession sqlSession = null; try { // 创建数据库会话实例sqlSession sqlSession = sqlSessionFactory.openSession(); // 查询单个记录,根据用户id查询用户信息 List<User> list = sqlSession.selectList("test.findUserByUsername", "张"); System.out.println(list.size()); } catch (Exception e) { e.printStackTrace(); } finally { if (sqlSession != null) { sqlSession.close(); } } } }
#{}和${}
#{}表示一个占位符号,通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以有效防止sql注入。 #{}可以接收简单类型值或pojo属性值。 如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。
${}表示拼接sql串,通过${}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换, ${}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,${}括号中只能是value。
parameterType和resultType
parameterType:指定输入参数类型,mybatis通过ognl从输入对象中获取参数值拼接在sql中。
resultType:指定输出结果类型,mybatis将sql查询结果的一行记录数据映射为resultType指定类型的对象。
selectOne和selectList
selectOne查询一条记录,如果使用selectOne查询多条记录则抛出异常:
org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 3
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:70)
selectList可以查询一条或多条记录。
添加
<!-- 添加用户 --> <insert id="insertUser" parameterType="cn.atwyl.mybatis.po.User"> <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer"> select LAST_INSERT_ID() </selectKey> insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address}) </insert>
<insert id="insertUser" parameterType="User" >
insert into cocode_member_case
<trim prefix="(" suffix=")" suffixOverrides="," >
<if test="caseId != null" >
case_id,
</if>
<if test="memberId != null" >
member_id,
</if>
<if test="caseName != null" >
case_name,
</if>
<if test="remark != null" >
remark,
</if>
<if test="imageUrl != null" >
image_url,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides="," >
<if test="caseId != null" >
#{caseId,jdbcType=INTEGER},
</if>
<if test="memberId != null" >
#{memberId,jdbcType=INTEGER},
</if>
<if test="caseName != null" >
#{caseName,jdbcType=VARCHAR},
</if>
<if test="remark != null" >
#{remark,jdbcType=VARCHAR},
</if>
<if test="imageUrl != null" >
#{imageUrl,jdbcType=VARCHAR},
</if>
</trim>
</insert>
测试程序:
// 添加用户信息 @Test public void testInsert() { // 数据库会话实例 SqlSession sqlSession = null; try { // 创建数据库会话实例sqlSession sqlSession = sqlSessionFactory.openSession(); // 添加用户信息 User user = new User(); user.setUsername("张小明"); user.setAddress("河南郑州"); user.setSex("1"); user.setPrice(1999.9f); sqlSession.insert("test.insertUser", user); //提交事务 sqlSession.commit(); } catch (Exception e) { e.printStackTrace(); } finally { if (sqlSession != null) { sqlSession.close(); } } }
mysql自增主键返回
<insert id="insertUser" parameterType="cn.atwyl.mybatis.po.User"> <!-- selectKey将主键返回,需要再返回 --> <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer"> select LAST_INSERT_ID() </selectKey> insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address}); </insert>
添加selectKey实现将主键返回
keyProperty:返回的主键存储在pojo中的哪个属性
order:selectKey的执行顺序,是相对与insert语句来说,由于mysql的自增原理执行完insert语句之后才将主键生成,所以这里selectKey的执行顺序为after
resultType:返回的主键是什么类型
LAST_INSERT_ID():是mysql的函数,返回auto_increment自增列新记录id值。
Mysql使用 uuid实现主键
需要增加通过select uuid()得到uuid值
<insert id="insertUser" parameterType="cn.atwyl.mybatis.po.User"> <selectKey resultType="java.lang.String" order="BEFORE" keyProperty="id"> select uuid() </selectKey> insert into user(id,username,birthday,sex,address) values(#{id},#{username},#{birthday},#{sex},#{address}) </insert> //注意这里使用的order是“BEFORE”
删除
<!-- 删除用户 --> <delete id="deleteUserById" parameterType="int"> delete from user where id=#{id} </delete>
测试程序:
// 根据id删除用户 @Test public void testDelete() { // 数据库会话实例 SqlSession sqlSession = null; try { // 创建数据库会话实例sqlSession sqlSession = sqlSessionFactory.openSession(); // 删除用户 sqlSession.delete("test.deleteUserById",18); // 提交事务 sqlSession.commit(); } catch (Exception e) { e.printStackTrace(); } finally { if (sqlSession != null) { sqlSession.close(); } } }
修改
<!-- 更新用户 --> <update id="updateUser" parameterType="cn.atwyl.mybatis.po.User"> update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id} </update>
<update id="updateByPrimaryKeySelective" parameterType="user" > update cocode_member_case <set > <if test="memberId != null" > member_id = #{memberId,jdbcType=INTEGER}, </if> <if test="caseName != null" > case_name = #{caseName,jdbcType=VARCHAR}, </if> <if test="remark != null" > remark = #{remark,jdbcType=VARCHAR}, </if> <if test="imageUrl != null" > image_url = #{imageUrl,jdbcType=VARCHAR}, </if> </set> where case_id = #{caseId,jdbcType=INTEGER} </update>
测试程序
// 更新用户信息 @Test public void testUpdate() { // 数据库会话实例 SqlSession sqlSession = null; try { // 创建数据库会话实例sqlSession sqlSession = sqlSessionFactory.openSession(); // 添加用户信息 User user = new User(); user.setId(16); user.setUsername("张小明"); user.setAddress("河南郑州"); user.setSex("1"); user.setPrice(1999.9f); sqlSession.update("test.updateUser", user); // 提交事务 sqlSession.commit(); } catch (Exception e) { e.printStackTrace(); } finally { if (sqlSession != null) { sqlSession.close(); } } }
1.3.7 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定义输出结果的类型。
1.4 hibernate不同
Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句,不过mybatis可以通过XML或注解方式灵活配置要运行的sql语句,并将java对象和sql语句映射生成最终执行的sql,最后将sql执行的结果再映射生成java对象。
Mybatis学习门槛低,简单易学,程序员直接编写原生态sql,可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。
Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用hibernate开发可以节省很多代码,提高效率。但是Hibernate的学习门槛高,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡,以及怎样用好Hibernate需要具有很强的经验和能力才行。
总之,按照用户的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。
2 Dao开发方法
使用Mybatis开发Dao,通常有两个方法,即原始Dao开发方法和Mapper接口开发方法。
2.1 需求
将下边的功能实现Dao:
根据用户id查询一个用户信息
根据用户名称模糊查询用户信息列表
添加用户信息
2.2 SqlSession的使用范围
SqlSession中封装了对数据库的操作,如:查询、插入、更新、删除等。
通过SqlSessionFactory创建SqlSession,而SqlSessionFactory是通过SqlSessionFactoryBuilder进行创建。
2.2.1 SqlSessionFactoryBuilder
SqlSessionFactoryBuilder用于创建SqlSessionFacoty,SqlSessionFacoty一旦创建完成就不需要SqlSessionFactoryBuilder了,因为SqlSession是通过SqlSessionFactory生产,所以可以将SqlSessionFactoryBuilder当成一个工具类使用,最佳使用范围是方法范围即方法体内局部变量。
2.2.2 SqlSessionFactory
SqlSessionFactory是一个接口,接口中定义了openSession的不同重载方法,SqlSessionFactory的最佳使用范围是整个应用运行期间,一旦创建后可以重复使用,通常以单例模式管理SqlSessionFactory。
2.2.3 SqlSession
SqlSession是一个面向用户的接口, sqlSession中定义了数据库操作,默认使用DefaultSqlSession实现类。
执行过程如下:
1、 加载数据源等配置信息
Environment environment = configuration.getEnvironment();
2、 创建数据库链接
3、 创建事务对象
4、 创建Executor,SqlSession所有操作都是通过Executor完成,mybatis源码如下:
if (ExecutorType.BATCH == executorType) { executor = newBatchExecutor(this, transaction); } elseif (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } if (cacheEnabled) { executor = new CachingExecutor(executor, autoCommit); }
1、 SqlSession的实现类即DefaultSqlSession,此对象中对操作数据库实质上用的是Executor
结论:
每个线程都应该有它自己的SqlSession实例。SqlSession的实例不能共享使用,它也是线程不安全的。因此最佳的范围是请求或方法范围。绝对不能将SqlSession实例的引用放在一个类的静态字段或实例字段中。
打开一个 SqlSession;使用完毕就要关闭它。通常把这个关闭操作放到 finally 块中以确保每次都能执行关闭。如下:
SqlSession session = sqlSessionFactory.openSession(); try { // do work } finally { session.close(); }
2.3 Mapper动态代理方式
2.3.1 映射文件
定义mapper映射文件UserMapper.xml(内容同Users.xml),需要修改namespace的值为 UserMapper接口路径。将UserMapper.xml放在classpath 下mapper目录下。
<?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.atwyl.mybatis.mapper.UserMapper"> <!-- 根据id获取用户信息 --> <select id="findUserById" parameterType="int" resultType="cn.atwyl.mybatis.po.User"> select * from user where id = #{id} </select> <!-- 自定义条件查询用户列表 --> <select id="findUserByUsername" parameterType="java.lang.String" resultType="cn.atwyl.mybatis.po.User"> select * from user where username like '%${value}%' </select> <!-- 添加用户 --> <insert id="insertUser" parameterType="cn.atwyl.mybatis.po.User"> <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer"> select LAST_INSERT_ID() </selectKey> insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address}) </insert> </mapper>
2.3.2Mapper.java(接口文件)
/** * 用户管理mapper */ Public interface UserMapper { //根据用户id查询用户信息 public User findUserById(int id) throws Exception; //查询用户列表 public List<User> findUserByUsername(String username) throws Exception; //添加用户信息 public void insertUser(User user)throws Exception; }
接口定义有如下特点:
1、 Mapper接口方法名和Mapper.xml中定义的statement的id相同
2、 Mapper接口方法的输入参数类型和mapper.xml中定义的statement的parameterType的类型相同
3、 Mapper接口方法的输出参数类型和mapper.xml中定义的statement的resultType的类型相同
2.3.3 加载UserMapper.xml文件
修改SqlMapConfig.xml文件:
<!-- 加载映射文件 --> <mappers> <mapper resource="mapper/UserMapper.xml"/> </mappers>
测试
Public class UserMapperTest extends TestCase { private SqlSessionFactory sqlSessionFactory; protected void setUp() throws Exception { //mybatis配置文件 String resource = "sqlMapConfig.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); //使用SqlSessionFactoryBuilder创建sessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } Public void testFindUserById() throws Exception { //获取session SqlSession session = sqlSessionFactory.openSession(); //获取mapper接口的代理对象 UserMapper userMapper = session.getMapper(UserMapper.class); //调用代理对象方法 User user = userMapper.findUserById(1); System.out.println(user); //关闭session session.close(); } @Test public void testFindUserByUsername() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<User> list = userMapper.findUserByUsername("张"); System.out.println(list.size()); } Public void testInsertUser() throws Exception { //获取session SqlSession session = sqlSessionFactory.openSession(); //获取mapper接口的代理对象 UserMapper userMapper = session.getMapper(UserMapper.class); //要添加的数据 User user = new User(); user.setUsername("张三"); user.setBirthday(new Date()); user.setSex("1"); user.setAddress("北京市"); //通过mapper接口添加用户 userMapper.insertUser(user); //提交 session.commit(); //关闭session session.close(); } }
2.4 定义包装对象
定义包装对象将查询条件(pojo)以类组合的方式包装起来
public class QueryVo { private User user; //自定义用户扩展类 private UserCustom userCustom; }
说明:mybatis底层通过ognl从pojo中获取属性值:#{user.username},user即是传入的包装对象的属性。queryVo是别名,即上边定义的包装对象类型。
2.5 传递hashmap
<!-- 传递hashmap综合查询用户信息 --> <select id="findUserByHashmap" parameterType="hashmap" resultType="user"> select * from user where id=#{id} and username like '%${username}%' </select>
上边红色标注的是hashmap的key。
测试:
Public void testFindUserByHashmap()throws Exception{ //获取session SqlSession session = sqlSessionFactory.openSession(); //获限mapper接口实例 UserMapper userMapper = session.getMapper(UserMapper.class); //构造查询条件Hashmap对象 HashMap<String, Object> map = new HashMap<String, Object>(); map.put("id", 1); map.put("username", "管理员"); //传递Hashmap对象查询用户列表 List<User>list = userMapper.findUserByHashmap(map); //关闭session session.close(); }
异常测试:
传递的map中的key和sql中解析的key不一致。
测试结果没有报错,只是通过key获取值为空。
2.6 resultType(输出类型)
2.6.1 输出简单类型
<!-- 获取用户列表总数 --> <select id="findUserCount" parameterType="user" resultType="int"> select count(1) from user </select>
public int findUserCount(User user) throws Exception;
测试
Public void testFindUserCount() throws Exception{ //获取session SqlSession session = sqlSessionFactory.openSession(); //获取mapper接口实例 UserMapper userMapper = session.getMapper(UserMapper.class); User user = new User(); user.setUsername("管理员"); //传递Hashmap对象查询用户列表 int count = userMapper.findUserCount(user); //关闭session session.close(); }
总结:
输出简单类型必须查询出来的结果集有一条记录,最终将第一个字段的值转换为输出类型。
使用session的selectOne可查询单条记录。
2.6.2 输出pojo对象
<!-- 根据id查询用户信息 --> <select id="findUserById" parameterType="int" resultType="user"> select * from user where id = #{id} </select>
public User findUserById(int id) throws Exception;
测试
Public void testFindUserById() throws Exception { //获取session SqlSession session = sqlSessionFactory.openSession(); //获限mapper接口实例 UserMapper userMapper = session.getMapper(UserMapper.class); //通过mapper接口调用statement User user = userMapper.findUserById(1); System.out.println(user); //关闭session session.close(); }
2.6.4 输出pojo列表
<!-- 根据名称模糊查询用户信息 --> <select id="findUserByUsername" parameterType="string" resultType="user"> select * from user where username like '%${value}%' </select>
public List<User> findUserByUsername(String username) throws Exception;
测试
测试: Public void testFindUserByUsername()throws Exception{ //获取session SqlSession session = sqlSessionFactory.openSession(); //获限mapper接口实例 UserMapper userMapper = session.getMapper(UserMapper.class); //如果使用占位符号则必须人为在传参数中加% //List<User> list = userMapper.selectUserByName("%管理员%"); //如果使用${}原始符号则不用人为在参数中加% List<User> list = userMapper.findUserByUsername("管理员"); //关闭session session.close(); }
2.7resultMap
resultType可以指定pojo将查询结果映射为pojo,但需要pojo的属性名和sql查询的列名一致方可映射成功。
如果sql查询字段名和pojo的属性名不一致,可以通过resultMap将字段名和属性名作一个对应关系 ,resultMap实质上还需要将查询结果映射到pojo对象中。
resultMap可以实现将查询结果映射为复杂类型的pojo,比如在查询结果映射对象中包括pojo和list实现一对一查询和一对多查询。
由于上边的mapper.xml中sql查询列和Users.java类属性不一致,需要定义resultMap:userListResultMap将sql查询列和Users.java类属性对应起来
<id />:此属性表示查询结果集的唯一标识,非常重要。如果是多个字段为复合唯一约束则定义多个<id />。
Property:表示person类的属性。
Column:表示sql查询出来的字段名。
Column和property放在一块儿表示将sql查询出来的字段映射到指定的pojo类属性上。
<result />:普通结果,即pojo的属性。
public List<User> findUserListResultMap() throws Exception;
2.8 动态sql(重点)
2.8.1 If
<!-- 传递pojo综合查询用户信息 --> <select id="findUserList" parameterType="user" resultType="user"> select * from user where 1=1 <if test="id!=null and id!=''"> and id=#{id} </if> <if test="username!=null and username!=''"> and username like '%${username}%' </if> </select>
注意要做不等于空字符串校验。
2.8.2 Where
<select id="findUserList" parameterType="user" resultType="user"> select * from user <where> <if test="id!=null and id!=''"> and id=#{id} </if> <if test="username!=null and username!=''"> and username like '%${username}%' </if> </where> </select>
<where />可以自动处理第一个and。
2.8.3 foreach
向sql传递数组或List,mybatis使用foreach解析,如下:
2.8.3.1 pojo传递list
<if test="ids!=null and ids.size>0"> <foreach collection="ids" open=" and id in(" close=")" item="id" separator="," > #{id} </foreach> </if>
测试代码
List<Integer> ids = new ArrayList<Integer>(); ids.add(1);//查询id为1的用户 ids.add(10); //查询id为10的用户 queryVo.setIds(ids); List<User> list = userMapper.findUserList(queryVo);
3.8.3.2 传递单个List
<select id="selectUserByList" parameterType="java.util.List" resultType="user"> select * from user <where> <!-- 传递List,List中是pojo --> <if test="list!=null"> <foreach collection="list" item="item" open="and id in("separator=","close=")"> #{item.id} </foreach> </if> </where> </select>
public List<User> selectUserByList(List userlist) throws Exception;
Public void testselectUserByList()throws Exception{ //获取session SqlSession session = sqlSessionFactory.openSession(); //获限mapper接口实例 UserMapper userMapper = session.getMapper(UserMapper.class); //构造查询条件List List<User> userlist = new ArrayList<User>(); User user = new User(); user.setId(1); userlist.add(user); user = new User(); user.setId(2); userlist.add(user); //传递userlist列表查询用户列表 List<User>list = userMapper.selectUserByList(userlist); //关闭session session.close(); }
3.8.3.3传递单个数组(数组中是pojo)
<!-- 传递数组综合查询用户信息 --> <select id="selectUserByArray" parameterType="Object[]" resultType="user"> select * from user <where> <!-- 传递数组 --> <if test="array!=null"> <foreach collection="array" index="index" item="item" open="and id in("separator=","close=")"> #{item.id} </foreach> </if> </where> </select>
sql只接收一个数组参数,这时sql解析参数的名称mybatis固定为array,如果数组是通过一个pojo传递到sql则参数的名称为pojo中的属性名。
index:为数组的下标。
item:为数组每个元素的名称,名称随意定义
open:循环开始
close:循环结束
separator:中间分隔输出
public List<User> selectUserByArray(Object[] userlist) throws Exception;
测试
Public void testselectUserByArray()throws Exception{ //获取session SqlSession session = sqlSessionFactory.openSession(); //获限mapper接口实例 UserMapper userMapper = session.getMapper(UserMapper.class); //构造查询条件List Object[] userlist = new Object[2]; User user = new User(); user.setId(1); userlist[0]=user; user = new User(); user.setId(2); userlist[1]=user; //传递user对象查询用户列表 List<User>list = userMapper.selectUserByArray(userlist); //关闭session session.close(); }
如果数组中是简单类型则写为#{item},不用再通过ognl获取对象属性值了。
public List<User> selectUserByArray(Object[] userlist) throws Exception;
测试
Public void testselectUserByArray()throws Exception{ //获取session SqlSession session = sqlSessionFactory.openSession(); //获限mapper接口实例 UserMapper userMapper = session.getMapper(UserMapper.class); //构造查询条件List Object[] userlist = new Object[2]; userlist[0]=”1”; userlist[1]=”2”; //传递user对象查询用户列表 List<User>list = userMapper.selectUserByArray(userlist); //关闭session session.close(); }
2.9 Sql片段
<!-- 传递pojo综合查询用户信息 --> <select id="findUserList" parameterType="user" resultType="user"> select * from user <where> <if test="id!=null and id!=''"> and id=#{id} </if> <if test="username!=null and username!=''"> and username like '%${username}%' </if> </where> </select>
u 将where条件抽取出来:
<sql id="query_user_where"> <if test="id!=null and id!=''"> and id=#{id} </if> <if test="username!=null and username!=''"> and username like '%${username}%' </if> </sql>
u 使用include引用:
<select id="findUserList" parameterType="user" resultType="user"> select * from user <where> <include refid="query_user_where"/> </where> </select>
注意:如果引用其它mapper.xml的sql片段,则在引用时需要加上namespace,如下:
<include refid="namespace.sql片段”/>
3.0一对一查询
3.1方法一
SELECT orders.*, user.username, userss.address FROM orders, user WHERE orders.user_id = user.id
po类
public class OrdersCustom extends Orders { private String username;// 用户名称 private String address;// 用户地址 get/set。。。。 }
OrdersCustom类继承Orders类后OrdersCustom类包括了Orders类的所有字段,只需要定义用户的信息字段即可。
<!-- 查询所有订单信息 --> <select id="findOrdersList" resultType="cn.atwyl.mybatis.po.OrdersCustom"> SELECT orders.*, user.username, user.address FROM orders, user WHERE orders.user_id = user.id </select>
public List<OrdersCustom> findOrdersList() throws Exception;
Public void testfindOrdersList()throws Exception{ //获取session SqlSession session = sqlSessionFactory.openSession(); //获限mapper接口实例 UserMapper userMapper = session.getMapper(UserMapper.class); //查询订单信息 List<OrdersCustom> list = userMapper.findOrdersList(); System.out.println(list); //关闭session session.close(); }
方法二
使用resultMap,定义专门的resultMap用于映射一对一查询结果。
<select id="findOrdersListResultMap" resultMap="userordermap"> SELECT orders.*, user.username, user.address FROM orders, user WHERE orders.user_id = user.id </select>
<!-- 订单信息resultmap --> <resultMap type="cn.atwyl.mybatis.po.Orders" id="userordermap"> <!-- 这里的id,是mybatis在进行一对一查询时将user字段映射为user对象时要使用,必须写 --> <id property="id" column="id"/> <result property="user_id" column="user_id"/> <result property="number" column="number"/> <association property="user" javaType="cn.atwyl.mybatis.po.User"> <!-- 这里的id为user的id,如果写上表示给user的id属性赋值 --> <id property="id" column="user_id"/> <result property="username" column="username"/> <result property="address" column="address"/> </association> </resultMap>
association:表示进行关联查询单条记录
property:表示关联查询的结果存储在cn.atwyl.mybatis.po.Orders的user属性中
javaType:表示关联查询的结果类型
<id property="id" column="user_id"/>:查询结果的user_id列对应关联对象的id属性,这里是<id />表示user_id是关联查询对象的唯一标识。
<result property="username" column="username"/>:查询结果的username列对应关联对象的username属性。
public List<Orders> findOrdersListResultMap() throws Exception;
Public void testfindOrdersListResultMap()throws Exception{ //获取session SqlSession session = sqlSessionFactory.openSession(); //获限mapper接口实例 UserMapper userMapper = session.getMapper(UserMapper.class); //查询订单信息 List<Orders> list = userMapper.findOrdersList2(); System.out.println(list); //关闭session session.close(); }
使用association完成关联查询,将关联查询信息映射到pojo对象中。
3.1一对多查询
SELECT orders.*, user.username, user.address, 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
定义po类
在Orders类中加入User属性。
在Orders类中加入List<Orderdetail> orderdetails属性
<select id="findOrdersDetailList" resultMap="userorderdetailmap"> SELECT orders.*, user.username, user.address, 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>
<!-- 订单信息resultmap --> <resultMap type="cn.atwyl.mybatis.po.Orders" id="userorderdetailmap"> <id property="id"column="id"/> <result property="user_id" column="user_id"/> <result property="number" column="number"/> <association property="user" javaType="cn.atwyl.mybatis.po.User"> <id property="id" column="user_id"/> <result property="username" column="username"/> <result property="address" column="address"/> </association> <collection property="orderdetails" ofType="cn.atwyl.mybatis.po.Orderdetail"> <id property="id" column="orderdetail_id"/> <result property="items_id" column="items_id"/> <result property="items_num" column="items_num"/> </collection> </resultMap>
黄色部分和上边一对一查询订单及用户信息定义的resultMap相同,
collection部分定义了查询订单明细信息。
collection:表示关联查询结果集
property="orderdetails":关联查询的结果集存储在cn.atwyl.mybatis.po.Orders上哪个属性。
ofType="cn.atwyl.mybatis.po.Orderdetail":指定关联查询的结果集中的对象类型即List中的对象类型。
<id />及<result/>的意义同一对一查询。
3.2resultMap使用继承
<resultMap type="cn.atwyl.mybatis.po.Orders" id="userorderdetailmap" extends="userordermap"> <collection property="orderdetails" ofType="cn.atwyl.mybatis.po.Orderdetail"> <id property="id" column="orderdetail_id"/> <result property="items_id" column="items_id"/> <result property="items_num" column="items_num"/> </collection> </resultMap>
public List<Orders>findOrdersDetailList () throws Exception;
Public void testfindOrdersDetailList()throws Exception{ //获取session SqlSession session = sqlSessionFactory.openSession(); //获限mapper接口实例 UserMapper userMapper = session.getMapper(UserMapper.class); //查询订单信息 List<Orders> list = userMapper.findOrdersDetailList(); System.out.println(list); //关闭session session.close(); }
使用collection完成关联查询,将关联查询信息映射到集合对象
3.3多对多查询
SELECT orders.*, USER .username, USER .address, orderdetail.id orderdetail_id, orderdetail.items_id, orderdetail.items_num, 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 orderdetail.items_id = items.id
在User中添加List<Orders> orders 属性,在Orders类中加入List<Orderdetail> orderdetails属性
需要关联查询映射的信息是:订单、订单明细、商品信息
订单:一个用户对应多个订单,使用collection映射到用户对象的订单列表属性中
订单明细:一个订单对应多个明细,使用collection映射到订单对象中的明细属性中
商品信息:一个订单明细对应一个商品,使用association映射到订单明细对象的商品属性中。
<!-- 一对多查询 查询用户信息、关联查询订单、订单明细信息、商品信息 --> <resultMap type="cn.atwyl.mybatis.po.User" id="userOrderListResultMap"> <id column="user_id" property="id"/> <result column="username" property="username"/> <collection property="orders" ofType="cn.atwyl.mybatis.po.Orders"> <id column="id" property="id"/> <result property="number" column="number"/> <collection property="orderdetails" ofType="cn.atwyl.mybatis.po.Orderdetail"> <id column="orderdetail_id" property="id"/> <result property="ordersId" column="id"/> <result property="itemsId" column="items_id"/> <result property="itemsNum" column="items_num"/> <association property="items" javaType="cn.atwyl.mybatis.po.Items"> <id column="items_id" property="id"/> <result column="items_name" property="name"/> <result column="items_detail" property="detail"/> </association> </collection> </collection> </resultMap>
一对多是多对多的特例,如下需求:
查询用户购买的商品信息,用户和商品的关系是多对多关系。
需求1:
查询字段:用户账号、用户名称、用户性别、商品名称、商品价格(最常见)
企业开发中常见明细列表,用户购买商品明细列表,
使用resultType将上边查询列映射到pojo输出。
需求2:
查询字段:用户账号、用户名称、购买商品数量、商品明细(鼠标移上显示明细)
使用resultMap将用户购买的商品明细列表映射到user对象中。
resultType:
作用:
将查询结果按照sql列名pojo属性名一致性映射到pojo中。
场合:
常见一些明细记录的展示,比如用户购买商品明细,将关联查询信息全部展示在页面时,此时可直接使用resultType将每一条记录映射到pojo中,在前端页面遍历list(list中是pojo)即可。
resultMap:
使用association和collection完成一对一和一对多高级映射(对结果有特殊的映射要求)。
association:
作用:
将关联查询信息映射到一个pojo对象中。
场合:
为了方便查询关联信息可以使用association将关联订单信息映射为用户对象的pojo属性中,比如:查询订单及关联用户信息。
使用resultType无法将查询结果映射到pojo对象的pojo属性中,根据对结果集查询遍历的需要选择使用resultType还是resultMap。
collection:
作用:
将关联查询信息映射到一个list集合中。
场合:
为了方便查询遍历关联信息可以使用collection将关联信息映射到list集合中,比如:查询用户权限范围模块及模块下的菜单,可使用collection将模块映射到模块list中,将菜单列表映射到模块对象的菜单list属性中,这样的作的目的也是方便对查询结果集进行遍历查询。
如果使用resultType无法将查询结果映射到list集合中。
3.4 延迟加载
在mybatis核心配置文件中配置:
lazyLoadingEnabled、aggressiveLazyLoading
设置项 |
描述 |
允许值 |
默认值 |
lazyLoadingEnabled |
全局性设置懒加载。如果设为‘false’,则所有相关联的都会被初始化加载。 |
true | false |
false |
aggressiveLazyLoading |
当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载。 |
true | false |
true |
<settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> </settings>
3.4.1 一对一查询延迟加载
查询订单信息,关联查询用户信息。
默认只查询订单信息,当需要查询用户信息时再去查询用户信息。
<select id="findOrdersList3" resultMap="userordermap2"> SELECT orders.* FROM orders </select>
<!-- 订单信息resultmap --> <resultMap type="cn.atwyl.mybatis.po.Orders" id="userordermap2"> <id property="id" column="id"/> <result property="user_id" column="user_id"/> <result property="number" column="number"/> <association property="user" javaType="cn.atwyl.mybatis.po.User" select="findUserById" column="user_id"/> </resultMap>
association:
select="findUserById":指定关联查询sql为findUserById
column="user_id":关联查询时将users_id列的值传入findUserById
最后将关联查询结果映射至cn.atwyl.mybatis.po.User。
collection:
select:指定关联查询sql
column:关联查询时将users_id列的值传入
javaType:java的类型
ofType:集合中存放的java对象
public List<Orders> findOrdersList3() throws Exception;
Public void testfindOrdersList3()throws Exception{ //获取session SqlSession session = sqlSessionFactory.openSession(); //获限mapper接口实例 UserMapper userMapper = session.getMapper(UserMapper.class); //查询订单信息 List<Orders> list = userMapper.findOrdersList3(); System.out.println(list); //开始加载,通过orders.getUser方法进行加载 for(Orders orders:list){ System.out.println(orders.getUser()); } //关闭session session.close(); }
4.0查询缓存
4.1 mybatis缓存介绍
Mybatis一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。Mybatis默认开启一级缓存。
Mybatis二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递参数也相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。Mybatis默认没有开启二级缓存需要在setting全局参数中配置开启二级缓存。
4.2一级缓存
4.2.1 原理
一级缓存区域是根据SqlSession为单位划分的。
每次查询会先从缓存区域找,如果找不到从数据库查询,查询到数据将数据写入缓存。
Mybatis内部存储缓存使用一个HashMap,key为hashCode+sqlId+Sql语句。value为从查询出来映射生成的java对象
sqlSession执行insert、update、delete等操作commit提交后会清空缓存区域。
测试1 //获取session SqlSession session = sqlSessionFactory.openSession(); //获限mapper接口实例 UserMapper userMapper = session.getMapper(UserMapper.class); //第一次查询 User user1 = userMapper.findUserById(1); System.out.println(user1); //第二次查询,由于是同一个session则不再向数据发出语句直接从缓存取出 User user2 = userMapper.findUserById(1); System.out.println(user2); //关闭session session.close();
测试2 //获取session SqlSession session = sqlSessionFactory.openSession(); //获限mapper接口实例 UserMapper userMapper = session.getMapper(UserMapper.class); //第一次查询 User user1 = userMapper.findUserById(1); System.out.println(user1); //在同一个session执行更新 User user_update = new User(); user_update.setId(1); user_update.setUsername("李奎"); userMapper.updateUser(user_update); session.commit(); //第二次查询,虽然是同一个session但是由于执行了更新操作session的缓存被清空,这里重新发出sql操作 User user2 = userMapper.findUserById(1); System.out.println(user2);
4.3 二级缓存
4.3.1 原理
二级缓存区域是根据mapper的namespace划分的,相同namespace的mapper查询数据放在同一个区域,如果使用mapper代理方法每个mapper的namespace都不同,此时可以理解为二级缓存区域是根据mapper划分。
每次查询会先从缓存区域找,如果找不到从数据库查询,查询到数据将数据写入缓存。
Mybatis内部存储缓存使用一个HashMap,key为hashCode+sqlId+Sql语句。value为从查询出来映射生成的java对象
sqlSession执行insert、update、delete等操作commit提交后会清空缓存区域。
4.3.2 开启二级缓存:
在核心配置文件SqlMapConfig.xml中加入
<setting name="cacheEnabled" value="true"/>
|
描述 |
允许值 |
默认值 |
cacheEnabled |
对在此配置文件下的所有cache 进行全局性开/关设置。 |
true false |
true |
要在你的Mapper映射文件中添加一行: <cache /> ,表示此mapper开启二级缓存。
实现序列化
二级缓存需要查询结果映射的pojo对象实现java.io.Serializable接口实现序列化和反序列化操作,注意如果存在父类、成员pojo都需要实现序列化接口。
public class Orders implements Serializable
public class User implements Serializable
//获取session1 SqlSession session1 = sqlSessionFactory.openSession(); UserMapper userMapper = session1.getMapper(UserMapper.class); //使用session1执行第一次查询 User user1 = userMapper.findUserById(1); System.out.println(user1); //关闭session1 session1.close(); //获取session2 SqlSession session2 = sqlSessionFactory.openSession(); UserMapper userMapper2 = session2.getMapper(UserMapper.class); //使用session2执行第二次查询,由于开启了二级缓存这里从缓存中获取数据不再向数据库发出sql User user2 = userMapper2.findUserById(1); System.out.println(user2); //关闭session2 session2.close();
禁用二级缓存
在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。
<select id="findOrderListResultMap" resultMap="ordersUserMap" useCache="false">
刷新缓存
在mapper的同一个namespace中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。
设置statement配置中的flushCache="true" 属性,默认情况下为true即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。
如下:<insert id="insertUser" parameterType="cn.atwyl.mybatis.po.User" flushCache="true">
Mybatis Cache参数
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 – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
5.mybatis整合ehcache原理
mybatis提供二级缓存Cache接口,如下:
它的默认实现类:
通过实现Cache接口可以实现mybatis缓存数据通过其它缓存数据库整合,mybatis的特长是sql操作,缓存数据的管理不是mybatis的特长,为了提高缓存的性能将mybatis和第三方的缓存数据库整合,比如ehcache、memcache、redis等。
第一步:引入缓存的依赖包
maven坐标:
<dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.0.2</version> </dependency>
第二步:引入缓存配置文件
classpath下添加:ehcache.xml
内容如下:
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <diskStore path="F:developehcache" /> <defaultCache maxElementsInMemory="1000" maxElementsOnDisk="10000000" eternal="false" overflowToDisk="false" timeToIdleSeconds="120" timeToLiveSeconds="120" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache> </ehcache>
属性说明:
l diskStore:指定数据在磁盘中的存储位置。
l defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略
以下属性是必须的:
l maxElementsInMemory - 在内存中缓存的element的最大数目
l maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大
l eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
l overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上
以下属性是可选的:
l timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
l timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.
l diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
l diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作
l memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)
第三步:开启ehcache缓存
EhcacheCache是ehcache对Cache接口的实现:
修改mapper.xml文件,在cache中指定EhcacheCache。
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
根据需求调整缓存参数:
<cache type="org.mybatis.caches.ehcache.EhcacheCache" > <property name="timeToIdleSeconds" value="3600"/> <property name="timeToLiveSeconds" value="3600"/> <!-- 同ehcache参数maxElementsInMemory --> <property name="maxEntriesLocalHeap" value="1000"/> <!-- 同ehcache参数maxElementsOnDisk --> <property name="maxEntriesLocalDisk" value="10000000"/> <property name="memoryStoreEvictionPolicy" value="LRU"/> </cache>
应用场景
对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis二级缓存技术降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等。
实现方法如下:通过设置刷新间隔时间,由mybatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval,比如设置为30分钟、60分钟、24小时等,根据需求而定。
局限性
mybatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。