java工具——持久层工具mybatis
mybatis简介
MyBatis 本是apache的一个开源项目 iBatis, 2010年这个项目 由apache software foundation 迁移到了google code ,并且改名为MyBatis 。2013年11月迁移到Github 。
iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层 框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAOs)
mybatis作用
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。
MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。
持久化
将程序数据在持久状态和瞬间状态之间转化的机制,最好的例子就是将内存中的数据保存到磁盘就是一个持久化的过程
我们的程序在运行时说的持久化通常就是指将内存数据存在硬盘,比如保存在数据库文件、xml文件中
持久层
java中所说的持久层,大体上指将业务中操作数据库的代码统一抽离出来,形成了位于业务层和数据库之间的独立的层
常见的解决方案有:
mybatis优点和缺点
sql语句与代码分离,存放在xml配置文件中
优点:便于维护管理,不用在java代码中找这些语句,使项目能更好的符合开闭原则
缺点:JDBC方式可以用打断点的方式调试,但是mybatis不能,需要通过log4j日志输出日志信息帮助调试,然后再配置文件中修改
用逻辑标签控制动态SQL的拼接
优点:用标签代替写逻辑代码
缺点:拼接复杂SQL语句时,没有代码灵活,拼写比较复杂。
查询的结果集与java对象自动映射
优点:保证名称相同,配置好映射关系即可自动映射,或者,不配置映射关系,通过配置列名 = 字段名也可以完成自动映射
缺点:对开发人员缩写的SQL依赖很强
编写原生SQL
接近JDBC,比较灵活
对SQL语句依赖程度很高;并且属于半自动,数据库移植比较麻烦,比如mysql数据库变成Oracle数据库,部分sql语句需要调整
代理设计模式
代理模式分为:
静态代理
动态代理
代理的核心功能是方法增强
静态代理
角色分析
抽象角色:一般使用接口或者抽象类来实现
真实角色:被代理的角色
代理角色:代理真实角色;代理真实角色后,一般会做一些附属的操作
客户:使用代理角色来进行一些操作
例子
写一个接口
1 2 3 4 5 6 public interface Singer { public void sing () ; }
男歌手
1 2 3 4 5 6 7 8 9 10 11 12 public class MaleSinger implements Singer { private String name; public MaleSinger (String name) { this .name = name; } @Override public void sing () { System.out.println(this .name+"开始唱歌了" ); } }
经纪人
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Agent implements Singer { private Singer singer; public Agent (Singer singer) { this .singer = singer; } @Override public void sing () { System.out.println("和节目组谈好价格,曲目,场地...." ); singer.sing(); System.out.println("结算费用" ); } }
客户
1 2 3 4 5 6 7 public class Client { public static void main (String[] args) { Singer singer = new MaleSinger ("Bob.M" ); Singer agent = new Agent (singer); agent.sing(); } }
这个过程中我们直接接触的是经纪人,经济人在演出前后做了一些复杂的事情
优点
虽然整个演出的过程复杂了,但是歌手类并没有改变
公共的统一问题都交给了代理完成
公共业务进行拓展或变更时,可以更为方便
符合开闭原则、单一原则
缺点
动态代理
动态代理的角色和静态代理的一样
动态代理的代理类是动态生成的,静态代理中的代理是我们自己创建的
动态代理分为两类:
基于接口的动态代理——JDK动态代理
基于类的动态代理——cglib
现在用的比较多的是javasist来生成动态代理
JDK动态代理
核心:invocationHandler和Proxy,打开JDK帮助文档可查看
【invocationHandler:调用处理程序】
1 2 3 4 5 Object invoke (Object proxy,Method method, Object[] args) ;
【proxy:代理】
1 2 3 4 5 6 7 public Object getProxy () { return Proxy.new ProxyInstance (this .getClass().getClassLoader(), rent.getClass().getInterface(),this ); }
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Client { public static void main (String[] args) { Singer singer = new MaleSinger ("Bob.M" ); Singer agent = (Singer) Proxy.newProxyInstance(Client.class.getClassLoader(), singer.getClass().getInterfaces(), new InvocationHandler () { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { System.out.println("节目组找过了,需要演出,谈好费用..." ); Object invoke = method.invoke(singer, args); System.out.println("结算费用,下一次合作预约..." ); return invoke; } }); agent.sing(); } }
Proxy会利用java发反射机制,自动的为你的项目中创建一个代理类,使用如下语句,生成的动态代理,就会在编译后的文件中显示
1 2 3 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles" ,true ); System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles" ,"true" );
原因在此:
Singer代理产生类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Agent implements InvocationHandler { private Object object; public Agent (Object object) { this .object = object; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { System.out.println("节目组找过了,需要演出,谈好费用..." ); Object invoke = method.invoke(object, args); System.out.println("结算费用,下一次合作预约..." ); return invoke; } public static Object getProxy (Singer singer) { Agent agent = new Agent (singer); return Proxy.newProxyInstance(Agent.class.getClassLoader(),singer.getClass().getInterfaces(),agent); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Client { public static void main (String[] args) { System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles" ,"true" ); Singer singer = new MaleSinger ("Bob.M" ); Singer agent = (Singer) Agent.getProxy(singer); agent.sing(); Singer singer1 = new MaleSinger ("Bob.D" ); Singer agent1 = (Singer) Agent.getProxy(singer1); agent1.sing(); } }
万能代理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Agent implements InvocationHandler { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { System.out.println("打开资源" ); System.out.println(method.getName()); if (args != null ){ System.out.println(args[0 ].getClass().getSimpleName()); } System.out.println("关闭资源" ); return null ; } public static <T>T getProxy (Class<T> target) { Agent agent = new Agent (); Object o = Proxy.newProxyInstance(Agent.class.getClassLoader(), new Class []{target}, agent); return (T)o; } }
1 2 3 4 5 6 7 8 9 10 11 12 public class Client { public static void main (String[] args) { System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles" ,"true" ); Singer proxy = Agent.getProxy(Singer.class); proxy.sing(); IUserDao proxy1 = Agent.getProxy(IUserDao.class); proxy1.saveUser("Ender" ); } }
搭建环境
建立数据库
1 2 3 4 5 6 7 8 9 DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` INT(20) NOT NULL, `username` VARCHAR(30) DEFAULT NULL, `password` VARCHAR(30) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=INNODB DEFAULT CHARSET=utf8; INSERT into `user`(`id`,`username`,`password`)VALUE (1,'张三','123456'),(2,'李四','abcdef'),(3,'王五','987654');
编写实体类
此处用到lombok插件,可使用注释的方式在编译时生成构造函数,toString等方法
1 2 3 4 5 6 7 8 9 10 11 12 @Data @AllArgsConstructor @NoArgsConstructor public class User implements Serializable { private int id; private String username; private String password; }
Maven配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 <dependencies > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > 1.18.8</version > </dependency > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 3.8.2</version > <scope > test</scope > </dependency > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.5.6</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.23</version > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-compiler-plugin</artifactId > <version > 3.1</version > <configuration > <source > 11</source > <target > 11</target > <encoding > utf-8</encoding > </configuration > </plugin > </plugins > <resources > <resource > <directory > src/main/java</directory > <includes > <include > **/*.properties</include > <include > **/*.xml</include > </includes > <filtering > false</filtering > </resource > <resource > <directory > src/main/resources</directory > <includes > <include > **/*.properties</include > <include > **/*.xml</include > </includes > <filtering > false</filtering > </resource > </resources > </build >
Mybatis核心配置文件
mybatis-config.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 1.0//EN" "http://mybatis.org/schema/mybatis-3-config.dtd" > <configuration > <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 ="jdbc:mysql://localhost:3306/ssm?useSSL=true& useUnicode=true& characterEncoding=utf8" /> <property name ="username" value ="root" /> <property name ="password" value ="root" /> </dataSource > </environment > </environments > <mappers > <mapper resource ="mappers/UserMapper.xml" /> </mappers > </configuration >
实列对应的xml,在该文件中,可以直接添加sql语句来进行查询
1 2 3 4 5 6 7 8 9 10 <?xml version="1.0" encoding="UTF-8" ?> <!--对xml的约束--> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.ender.dao.UserMapper" > <select id="selectUsers" resultType="com.ender.entity.User" > select id,username,password from user </select> </mapper>
DAO层接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public interface UserMapper { List<User> selectUsers () ; User selectUserById (int id) ; }
测试
此处使用junit中的before和after将数据库的开关分离处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class TestUser { private SqlSession session; @Before public void before () { try { String resource = "mybatis-config.xml" ; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder ().build(inputStream); session = sqlSessionFactory.openSession(); } catch (IOException e) { e.printStackTrace(); } } @Test public void testSelectUsers () { UserMapper mapper = session.getMapper(UserMapper.class); List<User> users = mapper.selectUsers(); for (User user : users) { System.out.println(user); } } @After public void after () { session.commit(); session.close(); } }
CRUD
select
在接口中添加方法的声明:
1 2 3 4 5 6 7 8 public interface UserMapper { User selectUserById (int id) ; }
在UserMapper.xml中添加select语句
1 2 3 <select id ="selectUserById" resultType ="com.ender.entity.User" > select id,username,password from user where id = #{id} </select >
其中:
resultType:指定了返回值类型
parameterType:参数类型(具有自动映射自动转化的过程)
id:指定对应的方法
#{id}:sql中的变量,要保证大括号中的变量必须在User对象里有
#{}:占位符,其实就是PreparedStatement
处理这个变量
#{}与${}的区别
#{}的主要作用就是替换预编译语句{PreparedStatement
}中的占位符, ? [推荐使用]
1 2 INSERT INTO user (name) VALUE (#{name}); INSERT INTO user (name) VALUE (?);
1 2 INSERT INTO user (name) VALUE ('${name}' ); INSERT INTO user (name) VALUE ('赵六' );
insert
1 2 3 4 5 6 7 8 9 10 11 public interface UserMapper { int saveUser (User user) ; }
1 2 3 <insert id ="saveUser" parameterType ="com.ender.entity.User" > insert into user (username,password) value (#{username},#{password}) </insert >
update
1 2 3 4 5 6 7 8 9 10 11 public interface UserMapper { int updateUser (User user) ; }
1 2 3 <update id ="updateUser" parameterType ="com.ender.entity.User" > update user set username = #{username} , password = #{password} where id = #{id} </update >
delete
1 2 3 4 5 6 7 8 9 10 11 public interface UserMapper { int deleteUser (int id) ; }
1 2 3 <delete id ="deleteUser" parameterType ="java.lang.Integer" > delete from user where id = #{id} </delete >
操作注释
通常不推荐使用注释的方法,因为要保证代码遵循开闭原则,如果使用注释操作,今后修改时就需要修改源代码,违反了开闭原则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 public class TestAdmin { private SqlSession session; @Before public void before () { try { String resource = "mybatis-config.xml" ; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder ().build(inputStream); session = sqlSessionFactory.openSession(); } catch (IOException e) { e.printStackTrace(); } } @Test public void testSelectAdmins () { AdminMapper mapper = session.getMapper(AdminMapper.class); List<Admin> admins = mapper.selectAdmins(); for (Admin admin : admins) { System.out.println(admin); } } @Test public void testSelectAdminsById () { AdminMapper mapper = session.getMapper(AdminMapper.class); Admin admin = mapper.selectAdminById(1 ); System.out.println(admin); } @Test public void testSaveAdmin () { AdminMapper mapper = session.getMapper(AdminMapper.class); Admin admin = new Admin (4 , "赵六" , "123" ); int rows = mapper.saveAdmin(admin); System.out.println(rows); } @Test public void testUpdateAdmin () { AdminMapper mapper = session.getMapper(AdminMapper.class); Admin admin = new Admin (4 , "赵六2" , "dsjioa123" ); int rows = mapper.updateAdmin(admin); System.out.println(rows); } @Test public void testDeleteAdmin () { AdminMapper mapper = session.getMapper(AdminMapper.class); int rows = mapper.deleteAdmin(4 ); System.out.println(rows); } @After public void after () { session.commit(); session.close(); } }
注册时需要选择类进行注册
1 2 3 <mappers > <mapper class ="com.ender.dao.AdminMapper" /> </mappers >
模糊查找
Java代码中拼接字符串
1 2 3 4 string name = "%IT%" ;list<name> names = mapper.getUserByName(name);
1 2 3 <select id = "getUserByName" > select * from user where name like #{name} </select >
在配置文件中拼接
1 2 string name = "IT" ;list<name> names = mapper.getUserByName(name);
1 2 3 4 <select id ="" > select * from user where name like "%"#{name}"%" </select >
map的使用
map可以代替任何的实体类,所以当我们数据比较复杂时,可以适当考虑使用map来完成相关工作
配置文件
1 2 3 <select id ="getUserByParams" resultType ="map" > select id,username,password from user where username = #{name} </select >
方法
1 2 3 4 5 6 List<User> getUsersByParams (Map<String,String> map) ;
测试
1 2 3 4 5 6 7 8 9 10 @Test public void findByParams () { UserMapper mapper = session.getMapper(UserMapper.class); Map<String,String> map = new HashMap <String,String>(); map.put("name" ,"郑七" ); Lisr<User> users = mapper.getUsersByparams(map); for (User user: users){ System.out.println(user.getUsername()); } }
多个参数
对于单个参数的方法来说,mybatis将自动将参数对应到sql语句中,但若参数包含多个,可以使用如下方式
1 2 3 4 5 6 7 int saveAdmin (@Param("username") String username,@Param("password") String password) ;
1 2 3 <select id ="" > insert into admin (username,password) values (#{username},#{password}) </select >
使用多参数时,需要使用注释表明对应关系,其次还可以使用一个map传参,mybatis将通过key、value来处理关系
内置别名
mybatis内置别名:
Alias
Mapped Type
_byte
byte
_long
long
_short
short
_int
int
_integer
int
_double
double
_float
float
_boolean
boolean
string
String
byte
Byte
long
Long
short
Short
int
Integer
integer
Integer
double
Double
float
Float
boolean
Boolean
date
Date
decimal
BigDecimal
bigdecimal
BigDecimal
object
Obkect
map
Map
hashmap
HashMap
list
List
arraylist
ArrayList
collection
Collection
iterator
Iterator
自定义别名
在核心配置文件中加入
1 2 3 <typeAliases > <typeAlias type ="com.ender.entity.User" alias ="user" /> </typeAliases >
<typeAliases>
标签需要在<enviroment>
之前,基本顺序为:<properties>
、<settings>
、typeAlias
、typeHandlers
、、、、<environment>
<typeAlias>
标签中有type
和alice
两个属性
type
填写实体类的全类名,alias
可以不填,不填默认为类名,不区分大小写,alias
有值时以其中的值为准
1 2 3 <typeAliases > <package name ="com.ender.entity" /> </typeAliases >
<package>
标签为某个包下所有类起别名;name
属性填写包名。包名默认是类名,不区分大小写
1 @Alias() 注解 加在实体类上,为某个类起别名:例如`@Alias("User")`
mybatis配置文件
mybatis的配置文件分为:
核心配置文件
mapper配置文件
核心配置文件
mybatis-config.xml系统核心配置文件
核心配置文件主要配置mybatis一些基础组件和加载资源,核心配置文件中的元素常常能影响mybatis的整个运行过程
能配置的内容如下,顺序不能乱:
1 2 3 4 5 6 7 8 9 10 11 12 1. properties是一个配置属性的元素 2. settings设置,mybatis最为复杂的配置也是最重要的,会改变mybatis运行时候的行为 3. typeAliases别名(在Type AliasRegistry中可以看到mybatis提供了许多的系统别名) 4. typeHandlers 类型处理器(比如在预处理语句中设置一个参数或者从结果集中获取一个参数的时候,都会用到类型处理器,在TypeHandlerRegistry中定义了很多的类型处理器) 5. objectFactory 对象工厂 (mybatis在构建一个和结构或返回的时候,会使用一个objectFactory去构建pojo) 6. plugins 插件 7. environments 环境变量 1. environment 环境变量 1. transactionManager 事务管理器 2. dataSource 数据源 3. databaseIdProvider 数据库厂商标识 8. mappers 映射器
environments元素
为mybatis配置多环境运行,将SQL映射到多个不同的数据库上,必须指定其中一个为默认运行环境(通过default指定),如果想切换环境修改default的值即可
最常见的就是,生产环境和开发环境,两个环境切换必将导致数据库的切换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <environments default ="development" > <environment id ="development" > <transactionManager type ="JDBC" > <property name ="..." value ="..." /> </transactionManager > <dataSource type ="POOLED" > <property name ="driver" value ="${driver}" /> <property name ="url" value ="${url}" /> <property name ="username" value ="${username}" /> <property name ="password" value ="${password}" /> </dataSource > </environment > <environment id ="product" > <transactionManager type ="JDBC" > <property name ="..." value ="..." /> </transactionManager > <dataSource type ="POOLED" > <property name ="driver" value ="${driver}" /> <property name ="url" value ="${url}" /> <property name ="username" value ="${username}" /> <property name ="password" value ="${password}" /> </dataSource > </environment > </environments >
1 2 3 4 type="[UNPOOKED|POOKED|JNDI]" - unpooked:这个数据源的实现只是每次被请求时打开和关闭连接 - pooled:这种数据源的实现利用“池”的概念将JDBC连接对象组织起来,这是一种使得并发Web应用快速响应请求的流行处理方法 - jndi:这个数据源实现时为了能在如spring或应用服务器中使用,容器可以集中或在外部配hi数据源,然后放置一个JNDI上下文引用
数据源也有很多第三方的实现,比如druid,dbcp,c3p0等等…
这两种事物管理器类型都不需要设置任何属性
具体的一套环境,通过设置id进行区别,id保证唯一
子元素节点:transactionManager - [事物管理器]
1 2 <transactionManager type ="[JDBC | MANAGED]" />
mappers元素
对写好的mapper和xml进行统一管理
引入方式
1 2 3 4 5 6 7 <mappers > <mapper resource ="com/ender/dao/userMapper.xml" /> <mapper class ="com.ender.dao.AdminMapper" /> </mappers >
Mapper文件
1 2 3 4 5 6 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" ><mapper namespace ="com.xinzhi.mapper.UserMapper" > </mapper >
namespace:命名空间
namespace的命名必须跟某个接口同名
Properties元素
数据库连接信息我们最好放在一个单独的文件中。
1 2 3 4 5 driver =com.mysql.jdbc.Driver url =jdbc:mysql://localhost:3306/ssm? useSSL =true&useUnicode=true&characterEncoding=utf8 username =root password =root
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <configuration > <properties resource ="db.properties" /> <environments default ="development" > <environment id ="development" > <transactionManager type ="JDBC" /> <dataSource type ="POOLED" > <property name ="driver" value ="${driver}" /> <property name ="url" value ="${url}" /> <property name ="username" value ="${username}" /> <property name ="password" value ="${password}" /> </dataSource > </environment > </environments > <mappers > <mapper resource ="mapper/UserMapper.xml" /> </mappers > </configuration >
设定别名
详见别名
其他配置浏览
settings能对我的一些核心功能进行配置,如懒加载、日志实现、缓存开启关闭等
简单参数说明:
点击查看
设置参数 描述 有效值 默认值 cacheEnabled 该配置影响的所有映射 器中配置的缓存的全局 开关。 true | false true lazyLoadingEnabled 延迟加载的全局开关。 当开启时,所有关联对 象都会延迟加载。特定 关联关系中可通过设置 fetchType属性来覆盖该 项的开关状态。 true | false false useColumnLabel 使用列标签代替列名。 不同的驱动在这方面会 有不同的表现,具体可 参考相关驱动文档或通 过测试这两种不同的模 式来观察所用驱动的结 果。 true | false true useGeneratedKeys 允许 JDBC 支持自动生 成主键,需要驱动兼 容。如果设置为 true 则 这个设置强制使用自动 生成主键,尽管一些驱 动不能兼容但仍可正常 工作(比如 Derby)。 true | false False defaultStatementTimeout 设置超时时间,它决定 驱动等待数据库响应的 秒数。 Any positive integer Not S Not Set (null) mapUnderscoreToCamelCase 是否开启自动驼峰命名 规则(camel case)映 射,即从经典数据库列 名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 true | false False logPrefix 指定 MyBatis 增加到日 志名称的前缀。 Any String Not set logImpl 指定 MyBatis 所用日志 的具体实现,未指定时 将自动查找。 SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING Not set
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <settings > <setting name ="cacheEnabled" value ="true" /> <setting name ="lazyLoadingEnabled" value ="true" /> <setting name ="multipleResultSetsEnabled" value ="true" /> <setting name ="useColumnLabel" value ="true" /> <setting name ="useGeneratedKeys" value ="false" /> <setting name ="autoMappingBehavior" value ="PARTIAL" /> <setting name ="autoMappingUnknownColumnBehavior" value ="WARNING" /> <setting name ="defaultExecutorType" value ="SIMPLE" /> <setting name ="defaultStatementTimeout" value ="25" /> <setting name ="defaultFetchSize" value ="100" /> <setting name ="safeRowBoundsEnabled" value ="false" /> <setting name ="mapUnderscoreToCamelCase" value ="false" /> <setting name ="localCacheScope" value ="SESSION" /> <setting name ="jdbcTypeForNull" value ="OTHER" /> <setting name ="lazyLoadTriggerMethods" value ="equals,clone,hashCode,toString" /> </settings >
动态sql
概述
MyBatis提供了对SQL语句动态的组装能力,大量的判断都可以在 MyBatis的映射XML文件里面配置,以达到许多我们需要大量代码才能实现的功能,大大减少了我们编写代码的工作量。 动态SQL的元素
动态SQL中的元素
元素
作用
备注
if
判断语句
但条件分支判断
choose、when、otherwise
相当于java中的case when语句
多条件分支判断
trim、where、set
辅助元素
用于处理一些SQL拼装问题
foreach
循环语句
在in语句等列举调价常用
if元素(常用)
if元素相当于Java中的if语句,它常常与test属性联合使用。现在我们要根据username去查询用户,但是username是可选的,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <select id ="findUserById" resultType ="com.ender.entity.User" > select id,username,password from user where 1 =1 <if test ="id != null and id != ''" > AND id = #{id} </if > <if test ="username != null and username != ''" > AND username = #{username} </if > <if test ="password != null and password != ''" > AND password = #{password} </if > </select >
choose、when、otherwise元素
有些时候我们还需要多种条件的选择,在Java中我们可以使用switch、case、default语句,而在映射器 的动态语句中可以使用choose、when、otherwise元素
1 2 3 4 5 6 7 8 9 10 11 12 13 <select id ="select" resultType ="com.ender.entity.User" > SELECT * FROM user WHERE 1=1 <choose > <when test ="name != null and name != ''" > AND username LIKE concat('%', #{username}, '%') </when > <when test ="id != null" > AND id = #{id} </when > </choose > </select >
where元素
上面的select语句我们加了一个 1=1 的绝对true的语句,目的是为了防止语句错误,变成 SELECT * FROM student WHERE 这样where后没有内容的错误语句。这样会有点奇怪,此时可以使用where元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <select id ="findUserById" resultType ="com.xinzhi.entity.User" > select id,username,password from user <where > <if test ="id != null and id != ''" > AND id = #{id} </if > <if test ="username != null and username != ''" > AND username = #{username} </if > <if test ="password != null and password != ''" > AND password = #{password} </if > </where > </select >
trim元素
有时候我们要去掉一些特殊的SQL语法,比如常见的and、or,此时可以使用trim元素。trim元素意味着 我们需要去掉一些特殊的字符串,prefix代表的是语句的前缀,而prefixOverrides代表的是你需要去掉 的那种字符串,suffix表示语句的后缀,suffixOverrides代表去掉的后缀字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <insert id ="saveUser" parameterType ="com.ender.entity.User" > insert into user ( <trim suffixOverrides ="," > <if test ="username != null and username != ''" > username, </if > <if test ="password != null and password != ''" > password, </if > </trim > )value( <trim suffixOverrides ="," > <if test ="username != null and username != ''" > #{username}, </if > <if test ="password != null and password != ''" > #{password}, </if > </trim > ) </insert >
set元素
在update语句中,如果我们只想更新某几个字段的值,这个时候可以使用set元素配合if元素来完成。注 意:set元素遇到,会自动把,去掉。
1 2 3 4 5 6 7 8 9 10 11 12 <update id ="updateUser" parameterType ="com.ender.entity.User" > update user <set > <if test = "username != null and username != ''" > username = #{username} </if > <if test ="password != null and password != ''" > password = #{password} </if > </set > where id = #{id} </update >
foreach元素
foreach元素是一个循环语句,它的作用是遍历集合,可以支持数组、List、Set接口。
1 2 3 4 5 6 7 <select id ="select" resultType ="com.xinzhi.entity.User" > SELECT * FROM user WHERE id IN <foreach collection ="ids" open ="(" close =")" separator ="," item ="id" > #{id} </foreach > </select >
collection配置的是传递进来的参数名称
item配置的是循环中当前的元素。
index配置的是当前元素在集合的位置下标。
open和 close配置的是以什么符号将这些集合元素包装起来。
separator是各个元素的间隔符。
SQL片段
有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽 取出来,然后使用时直接调用。
1 2 3 <sql id ="user-all-content" > select id,username,password from user </sql >
引用
1 <include refid ="user-all-content" />
结果映射resultMap
数据库不可能永远是你所想或所需的那个样子
属性名和字段名不一致,我们一般都会按照约定去设计数据的,但确实阻止不了沟通不充分等问题
java中实体类的设计
1 2 3 4 5 6 7 8 public class User { private int id; private String name; private String password; }
mapper类
1 2 User selectUserById (int id) ;
mapper映射文件
1 2 3 <select id ="selectUserById" resultType ="user" > select * from user where id = #{id} </select >
测试
1 2 3 4 5 6 7 @Test public void testSelectUserById () { UserMapper mapper = session.getMapper(UserMapper.class); User user = mapper.selectUserById(1 ); System.out.println(user); session.close(); }
结果
User{id=1,name=‘null’,password=‘123’}
查询出来发现name为空
分析
select * from user where id = #{id} 可以看做 select id,username,password from user where id = #{id}
mybatis会根据这些查询的列名(会将列名转化为小写,数据库不区分大小写) , 利用反射去对应的实体类中查找相应列名的set方法设值 ,当然找不到username
解决方法
方案一
为列名指定别名,别名和java实体类的属性名一致
1 2 3 <select id ="selectUserById" resultType ="User" > select id , username as name ,password from user where id = #{id} </select >
方案二
使用结果集映射→ \to → ResultMap(推荐)
1 2 3 4 5 6 7 8 9 10 11 <resultMap id ="UserMap" type ="User" > <id column ="id" property ="id" /> <result column ="username" property ="name" /> <result column ="password" property ="password" /> </resultMap > <select id ="selectUserById" resultMap ="UserMap" > select id , username , password from user where id = #{id} </select >
常用映射
通常在数据库中我们会使用下划线的命名方式,而java中通常使用驼峰命名法,mybatis为我们提供了两者的自动转化
1 2 3 4 <settings > <setting name ="mapUnderscoreToCamelCase" value ="true" /> </settings >
日志配置
配置日志的一个重要原因是想在调试的时候能观察到sql语句的输出,能查看中间过程
标准日志实现
指定 MyBatis 应该使用哪个日志记录实现。如果此设置不存在,则会自动发现日志记录实现。
STD:standard out:输出
STDOUT_LOGGING:标准输出日志
1 2 3 <settings > <setting name ="logImpl" value ="STDOUT_LOGGING" /> </settings >
组合log4j完成日志功能
导入log4j
1 2 3 4 5 6 7 8 9 10 <dependency > <groupId > log4j</groupId > <artifactId > log4j</artifactId > <version > 1.2.12</version > </dependency > <dependency > <groupId > org.slf4j</groupId > <artifactId > slf4j-nop</artifactId > <version > 1.7.30</version > </dependency >
配置文件编写log4j.properties
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 码 log4j.rootLogger =DEBUG,console,file log4j.appender.console = org.apache.log4j.ConsoleAppender log4j.appender.console.Target = System.out log4j.appender.console.Threshold =DEBUG log4j.appender.console.layout = org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern =[%c]-%m%n log4j.appender.file = org.apache.log4j.RollingFileAppender log4j.appender.file.File =./log/xinzhi.log log4j.appender.file.MaxFileSize =10mb log4j.appender.file.Threshold =DEBUG log4j.appender.file.layout =org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern =[%p][%d{yy-MM-dd}][%c]%m%n log4j.logger.org.mybatis =DEBUG log4j.logger.java.sql =DEBUG log4j.logger.java.sql.Statement =DEBUG log4j.logger.java.sql.ResultSet =DEBUG log4j.logger.java.sql.PreparedStatement =DEBUG
setting设置日志实现
1 2 3 <settings > <setting name ="logImpl" value ="LOG4J" /> </settings >
在程序中使用Log4j进行输出
1 2 3 4 5 6 7 8 @Test public void findAllUsers () { UserMapper mapper = session.getMapper(UserMapper.class); List<User> users = mapper.selectUser(); for (User user: users){ System.out.println(user); } }
多表查询
数据库设计
部门和员工的关系为:一个部门多个员工,一个员工属于一个部门
依次我们可以实际一个一对多的数据库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 CREATE TABLE `dept` ( `id` INT(10) NOT NULL, `name` VARCHAR(30) DEFAULT NULL, PRIMARY KEY (`id`) ); INSERT INTO dept VALUES (1, 'ender开发六部'); CREATE TABLE `employee` ( `id` INT(10) NOT NULL, `name` VARCHAR(30) DEFAULT NULL, `did` INT(10) DEFAULT NULL, PRIMARY KEY (`id`), CONSTRAINT `fk_did` FOREIGN KEY (`did`) REFERENCES `dept` (`id`) ); INSERT INTO employee VALUES (1, '小兰', 1); INSERT INTO employee VALUES (2, '小红', 1); INSERT INTO employee VALUES (3, '小白', 1); INSERT INTO employee VALUES (4, '小黑', 1); INSERT INTO employee VALUES (5, '小黄', 1);
在多的一方维护关系
方法一:结果集嵌套
编写实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Data public class Dept implements Serializable { private int id; private String name; } @Data public class Employee implements Serializable { private int id; private String name; private Dept dept; }
编写实体类对应的Mapper接口
1 2 3 4 public interface DeptMapper {} public interface EmployeeMapper {}
编写mapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.ender.mapper.EmployeeMapper" > </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 ="com.ender.mapper.DeptMapper" > </mapper >
编写方法
1 2 3 4 5 6 Employee findEmployeesById (Integer id) ;
mapper处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.ender.mapper.EmployeeMapper" > <resultMap id ="EmployeeDept" type ="com.ender.entity.Employee" > <id property ="id" column ="eid" /> <result property ="name" column ="ename" /> <association property ="dept" javaType ="com.ender.entity.Dept" > <id property ="id" column ="did" /> <result property ="name" column ="dname" /> </association > </resultMap > <select id ="findEmployeesById" parameterType ="int" resultMap ="EmployeeDept" > SELECT e.id eid, e.name ename,d.id did,d.name dname FROM employee e LEFT JOIN dept d on e.did = d.id where e.id = #{id} </select > </mapper >
注册Mapper
1 2 <mapper resource ="mappers/DeptMapper.xml" /> <mapper resource ="mappers/employeeMapper.xml" />
测试
1 2 3 4 5 6 @Test public void testFindEmployessById () { EmployeeMapper mapper = session.getMapper(EmployeeMapper.class); Employee employee = mapper.findEmployeesById(1 ); System.out.println(employee); }
方法二:查询嵌套
编写mapper.xml
employeeMapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.ender.mapper.EmployeeMapper" > <resultMap id ="EmployeeDept" type ="com.ender.entity.Employee" > <association property ="dept" javaType ="com.ender.entity.Dept" select ="com.ender.mapper.DeptMapper.findDeptById" column ="did" /> </resultMap > <select id ="findEmployeesById" resultMap ="EmployeeDept" > SELECT id,name,did FROM employee WHERE id = #{id} </select > </mapper >
deptMapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.ender.mapper.DeptMapper" > <resultMap id ="DeptResult" type ="com.ender.entity.Dept" > <id property ="id" column ="did" /> </resultMap > <select id ="findDeptById" resultMap ="DeptResult" > SELECT id did,name FROM dept WHERE id = #{id} </select > </mapper >
在一的一方维护关系
即查找所有部门时,将该部门下的所有员工一并查找出来
方法一:结果集嵌套
实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Data @AllArgsConstructor @NoArgsConstructor public class Dept implements Serializable { private int id; private String name; List<Employee> employees; }
方法
1 2 3 4 5 6 7 8 9 10 11 public interface DeptMapper { List<Dept> findDepts () ; }
Mapper配置
DeptMapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.ender.mapper.DeptMapper" > <resultMap id ="DeptEmployees" type ="dept" > <id property ="id" column ="did" /> <result property ="name" column ="dname" /> <collection property ="employees" ofType ="employee" > <id property ="id" column ="eid" /> <result property ="name" column ="ename" /> </collection > </resultMap > <select id ="findDepts" resultMap ="DeptEmployees" > SELECT d.id did,d.name dname,e.id eid,e.name ename FROM dept d LEFT JOIN employee e on d.id = e.did </select > </mapper >
方法二:查询嵌套
Mapper配置
DeptMapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.ender.mapper.DeptMapper" > <resultMap id ="deptEmpResult" type ="dept" > <id property ="id" column ="id" /> <collection property ="employees" ofType ="employee" column ="id" select ="com.ender.mapper.EmployeeMapper.findEmployeesByDid" /> </resultMap > <select id ="findDepts" resultMap ="deptEmpResult" > SELECT id,name FROM dept </select > </mapper >
EmployeeMapper.xml
1 2 3 4 5 6 7 8 9 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.ender.mapper.EmployeeMapper" > <select id ="findEmployeesByDid" resultType ="employee" > SELECT id,name,did FROM employee WHERE did = #{id} </select > </mapper >
Mybatis缓存
为什么需要缓存
如果缓存中有数据,就不用从数据库获取,大大提高系统性能
mybatis提供一级缓存和二级缓存
一级缓存
一级缓存是sqlsession级别的缓存
操作数据库时,需要构造sqlsession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据
不同的sqlsession之间的缓存区域时互不影响的
一级缓存工作原理
第一次发起查询sql查询用户id为1的用户,先去找缓存中是否有id为1的用户,如果没有,再去数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。
如果sqlsession执行了commit操作(插入,更新,删除),会清空sqlsession中的一级缓存,避免脏读
第二次发起查询id为1的用户,缓存中如果找到了,直接从缓存中获取用户信息 mybatis默认支持并开启一级缓存。
可以通过mybatis写入日志时是否调用了MySQL语句来判断缓存是否被调用
测试
编写接口方法
1 2 User findUserById (@Param("id") int id) ;
Mapper配置
1 2 3 <select id ="findUserById" resultType ="com.ender.entity.User" > select * from user where id = #{id} </select >
测试
1 2 3 4 5 6 7 8 9 10 @Test public void testFindUserById () {UserMapper mapper = session.getMapper(UserMapper.class);User user1 = mapper.findUserById(1 );System.out.println(user1); User user2 = mapper.findUserById(3 );System.out.println(user2); User user3 = mapper.findUserById(1 );System.out.println(user3); }
查看日志
1 2 3 4 5 6 7 8 9 10 11 12 [com.xinzhi.dao.UserMapper.findUserById]-==> Preparing: select id,username,password from user where id = ? [com.xinzhi.dao.UserMapper.findUserById]-==> Parameters: 1(Integer) [com.xinzhi.dao.UserMapper.findUserById]-<== Total: 1 User{id=1, username='楠哥', password='123456'} ---->ID为1,第一次有sql [com.xinzhi.dao.UserMapper.findUserById]-==> Preparing: select id,username,password from user where id = ? [com.xinzhi.dao.UserMapper.findUserById]-==> Parameters: 3(Integer) [com.xinzhi.dao.UserMapper.findUserById]-<== Total: 1 User{id=3, username='磊哥', password='987654'} ---->ID为3,第一次有sql User{id=1, username='楠哥', password='123456'} ---->ID为1,第二次无sql, 走缓存
一级缓存失效
sqlSession不同
当sqlSession对象相同的时候,查询的条件不同,原因是第一次查询时候一级缓存中没有第二次查询所需要的数据
当sqlSession对象相同,两次查询之间进行了插入的操作
当sqlSession对象相同,手动清除了一级缓存中的数据
二级缓存
二级缓存时mapper级别的缓存,默认关闭
多个SQL session去操作同一个mapper的sql语句,多个sqlsession可以共用二级缓存,所得到的数据会存在二级缓存区域
二级缓存时跨sqlsession的
二级缓存相比一级缓存的范围更大(按namespace划分),多个sqlsession可以共享一个二级缓存
打开二级缓存
首先要手动开启mybatis二级缓存。 在config.xml设置二级缓存开关 , 还要在具体的mapper.xml开启二级缓存
1 2 3 4 <settings > <setting name ="cacheEnabled" value ="true" /> </settings >
1 2 <!-- 需要将映射的javabean类实现序列化 --> class Student implements Serializable {}
1 2 <cache eviction ="LRU" flushInterval ="100000" />
cache属性的简介
eviction
回收策略(缓存满了的淘汰机制),目前MyBatis提供以下策略。
LRU(Least Recently Used),最近最少使用的,最长时间不用的对象
FIFO(First In First Out),先进先出,按对象进入缓存的顺序来移除他们
SOFT,软引用,移除基于垃圾回收器状态和软引用规则的对象
WEAK,弱引用,更积极的移除基于垃圾收集器状态和弱引用规则的对象。这里采用的是LRU, 移除最长时间不用的对形象
flushInterval
刷新间隔时间,单位为毫秒
这里配置的是100秒刷新,如果你不配置它,那么当SQL被执行的时候才会去刷新缓存。
size
引用数目
一个正整数,代表缓存最多可以存储多少个对象,不宜设置过大。设置过大会导致内存溢出。 这里配置的是1024个对象
readOnly
只读
意味着缓存数据只能读取而不能修改,这样设置的好处是我们可以快速读取缓存,缺点是我们没有办法修改缓存,他的默认值是false,不允许我们修改
操作过程
sqlsession1查询用户id为1的信息,查询到之后,会将查询数据存储到二级缓存中。
如果sqlsession3去执行相同mapper下sql,执行commit提交,会清空该mapper下的二级缓存区域的数据
sqlsession2查询用户id为1的信息,去缓存找是否存在缓存,如果存在直接从缓存中取数据
禁用二级缓存
在statement中可以设置useCache=false,禁用当前select语句的二级缓存,默认情况为true
1 2 <select id ="getStudentById" parameterType ="java.lang.Integer" resultType ="Student" useCache ="false" >
在实际开发中,针对每次查询都需要最新的数据sql,要设置为useCache=“false” ,禁用二级缓存
flushCache标签
刷新缓存(清空缓存)
1 2 <select id ="getStudentById" parameterType ="java.lang.Integer" resultType ="Student" flushCache ="true" >
一般下执行完commit操作都需要刷新缓存,flushCache="true 表示刷新缓存,可以避免脏读
脏读:当数据保存在缓存中时,使用修改操作将数据库中的数据修改,此时,缓存中的数据和数据库中的不一样,于是下次读操作若从缓存中读取,则会出现读到的数据不是正确的数据的情况
二级缓存应用场景
对于访问多的查询请求并且用户对查询结果实时性要求不高的情况下,可采用mybatis二级缓存,降低 数据库访问量,提高访问速度,如电话账单查询 根据需求设置相应的flushInterval:刷新间隔时间,比如三十分钟,24小时等。
二级缓存局限性
mybatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,由于商品 信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就 无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓 存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类 问题需要在业务层根据需求对数据有针对性缓存。
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void testFindUserCache () throws Exception { UserMapper mapper1 = session.getMapper(UserMapper.class); User user1 = mapper1.findUserById(1 ); System.out.println(user1); session.commit(); UserMapper mapper2 = session.getMapper(UserMapper.class); User user2 = mapper2.findUserById(1 ); System.out.println(user2); System.out.println(user1 == user2); }
结果
1 2 3 4 5 6 7 8 9 10 11 [com.ender.dao.UserMapper.findUserById]-==> Preparing: select id,username,password from user where id = ? [com.ender.dao.UserMapper.findUserById]-==> Parameters: 1(Integer) [com.ender.dao.UserMapper.findUserById]-<== Total: 1 User{id=1, username='ender', password='123456'} [com.ender.dao.UserMapper.findUserById]-==> Preparing: select id,username,password from user where id = ? [com.ender.dao.UserMapper.findUserById]-==> Parameters: 1(Integer) [com.ender.dao.UserMapper.findUserById]-<== Total: 1 User{id=1, username='ender', password='123456'} false ---->两个对象不是一个,发了两个sql,说明缓存没有起作用
可以看见两次同样的sql,却都进库进行了查询。说明二级缓存没开。
开启二级缓存
1 <setting name ="cacheEnabled" value ="true" />
Mapper
1 2 3 4 5 6 7 <cache eviction ="LRU" flushInterval ="100000" size ="512" readOnly ="true" > </cache >
测试
1 2 3 4 5 6 7 8 [com.ender.dao.UserMapper.findUserById]-==> Preparing: select id,username,password from user where id = ? [com.ender.dao.UserMapper.findUserById]-==> Parameters: 1(Integer) [com.ender.dao.UserMapper.findUserById]-<== Total: 1 User{id=1, username='ender', password='123456'} [com.ender.dao.UserMapper]-Cache Hit Ratio [com.ender.dao.UserMapper]: 0.5 User{id=1, username='ender', password='123456'} true ---->两个对象一样了,就发了一个sql,说明缓存起了作用
第三方缓存——EhCache充当三级缓存
我们的三方缓存组件很对,最常用的比如ehcache,Memcached、redis等,我们以比较简单的 ehcache为例。
引入依赖
1 2 3 4 5 6 <dependency > <groupId > org.mybatis.caches</groupId > <artifactId > mybatis-ehcache</artifactId > <version > 1.2.1</version > </dependency >
Mapper
1 2 3 4 <mapper namespace = "com.ender.entity.User" > <cache type ="org.mybatis.caches.ehcache.EhcacheCache" eviction ="LRU" flushInterval ="10000" size ="1024" readOnly ="true" /> </mapper >
ehcache配置
添加ehcache.xml文件,ehcache配置文件,具体配置自行百度
点击查看配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <?xml version="1.0" encoding="UTF-8" ?> <ehcache xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation ="http://ehcache.org/ehcache.xsd" updateCheck ="false" > <diskStore path ="./tmpdir/Tmp_EhCache" /> <defaultCache eternal ="false" maxElementsInMemory ="10000" overflowToDisk ="false" diskPersistent ="false" timeToIdleSeconds ="1800" timeToLiveSeconds ="259200" memoryStoreEvictionPolicy ="LRU" /> </ehcache >
测试
1 2 3 4 5 6 7 8 [com.ender.dao.UserMapper.findUserById]-==> Preparing: select id,username,password from user where id = ? [com.ender.dao.UserMapper.findUserById]-==> Parameters: 1(Integer) [com.ender.dao.UserMapper.findUserById]-<== Total: 1 User{id=1, username='ender', password='123456'} [com.ender.dao.UserMapper]-Cache Hit Ratio [com.ender.dao.UserMapper]: 0.5 User{id=1, username='ender', password='123456'} true
其实我们更加常见的是使用第三方的缓存进行存储,并且自由控制
1 2 3 4 5 <dependency > <groupId > net.sf.ehcache</groupId > <artifactId > ehcache</artifactId > <version > 2.10.3</version > </dependency >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 final CacheManager cacheManager = new CacheManager (this .getClass().getClassLoader().getResourceAsStream("ehcache.x ml" ));String[] cacheNames = cacheManager.getCacheNames(); for (String cacheName : cacheNames) { System.out.println(cacheName); } Cache userDao = cacheManager.getCache("userDao" );Element element = new Element ("testFindUserById_1" ,new User (1 ,"q" ,"d" ));userDao.put(element); Element element1 = userDao.get("testFindUserById_1" );User user = (User)element1.getObjectValue();System.out.println(user);
参考笔记