简介
什么是Mybatis
来自官网文档:
-
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。
-
MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
-
MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
来自百度百科:
-
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。
-
iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAOs)
持久化
- 内存具有断点即失的特点
- 数据需要存放在数据库(JDBC)或文件(IO)中
持久层
Dao层,Service层,Controller层······
- 完成持久化工作的代码块
- 界限十分明显
获取Mybatis
-
Maven
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> -
Github
第一个Mybatis程序
环境搭建
-
Java
-
Maven
-
Mysql
DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` int(11) NOT NULL, `username` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `password` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of user -- ---------------------------- INSERT INTO `user` VALUES (1, 'volcano', '123321'); INSERT INTO `user` VALUES (2, 'candashuai', '123123'); INSERT INTO `user` VALUES (3, '张三', '123456'); SET FOREIGN_KEY_CHECKS = 1;
创建项目
-
创建项目
-
删除src目录
-
导入依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
</dependencies>
-
创建新module,可以使用主项目的依赖包
开始使用
编写配置文件
在src/main/resources下新建mybatis-config.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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 数据库驱动 -->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<!-- 数据库连接,&符号要用&替代,mysql8及以上需要添加时区配置 -->
<property name="url" value="jdbc:mysql://localhost:3306/smbms?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<!-- 用户名和密码 -->
<property name="username" value="root"/>
<property name="password" value="1q2w3e4r5t"/>
</dataSource>
</environment>
</environments>
<!-- 这里配置对应Mapper文件,相对路径是要对应在打包好的target -->
<mappers>
<mapper resource="com/volcano/dao/UserMapper.xml"/>
</mappers>
</configuration>
编写工具类
在src/main/java/com/volcano/utils中新建MybatisUtils.java
package com.volcano.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class MybatisUtils {
private static SqlSessionFactory SqlSessionFactory;
static {
try {
//官网文档写死的三句代码,封装成工具类
//此路径改为自己配置文件所在的路径,默认就是resources目录下
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
//true自动提交事务,默认为false,每次增删改都需要commit
return SqlSessionFactory.openSession();
}
}
创建实体类
在src/main/java/com/volcano/pojo下创建User.java
package com.volcano.pojo;
public class User {
private int id;
private String username;
private String password;
public User() {
}
public User(int id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
public void setId(int id) {
this.id = id;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public int getId() {
return id;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}
创建Dao
在src/main/java/com/volcano/dao下创建UserDao.java和UserMapper.xml
package com.volcano.dao;
import com.volcano.pojo.User;
import java.util.List;
// 后续的Dao会改名成Mapper
public interface UserDao {
//定义一个返回用户列表的方法
List<User> getUserList();
}
<?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">
<!-- 绑定命名空间为UserDao才能找到其中的方法 -->
<mapper namespace="com.volcano.dao.UserDao">
<!-- id为方法名和UserDao中对应 resultType为返回类型 -->
<select id="getUserList" resultType="com.volcano.pojo.User">
select * from mybatis.user
</select>
</mapper>
测试
在src/test/java下创建与main中相同的包结构(非必须),创建UserDaoTest.java
package com.volcano.dao;
import com.volcano.pojo.User;
import com.volcano.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class UserDaoTest {
@Test
public void test(){
//1.获取SqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//方式一:getMapper
UserDao mapper = sqlSession.getMapper(UserDao.class);
List<User> userList = mapper.getUserList();
//方式二:老方法不推荐
// List<User> userList = sqlSession.selectList("com.volcano.dao.UserDao.getUserList");
for (User user : userList) {
System.out.println(user);
}
//最好是放在finally中关闭
sqlSession.close();
}
}
项目结构
实现CRUD
先将之前的的UserDao改为UserMapper,并修改所有的引用。
-
修改
UserMapper.javapackage com.volcano.dao; import com.volcano.pojo.User; import java.util.List; public interface UserMapper { //定义一个返回用户列表的方法 List<User> getUserList(); //通过id查询用户 User getUserById(int id); //添加用户 int insertUser(User user); //删除用户 int deleteUser(int id); //修改用户信息 int updateUser(int id); } -
修改
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"> <!-- 绑定命名空间为UserDao才能找到其中的方法 --> <mapper namespace="com.volcano.dao.UserMapper"> <!-- id为方法名和UserDao中对应 resultType为返回类型 --> <select id="getUserList" resultType="com.volcano.pojo.User"> select * from mybatis.user </select> <!-- parameterType为int,getUserById(int id)参数类型 --> <!-- #{}为占位符,其中填写getUserById(int id)的形参名 --> <select id="getUserById" resultType="com.volcano.pojo.User" parameterType="int"> select * from mybatis.user where id = #{id} </select> <!-- 参数类型为User,在sql语句中直接使用User类中的属性,一一对应关系 --> <insert id="insertUser" parameterType="com.volcano.pojo.User"> insert into mybatis.user values (#{id},#{username},#{password}) </insert> <delete id="deleteUser" parameterType="int"> delete from mybatis.user where id = #{id} </delete> <update id="updateUser" parameterType="int"> update `user` set username = "candashuai222",password="999999" where id = #{id} </update> </mapper> -
测试
package com.volcano.dao; import com.volcano.pojo.User; import com.volcano.utils.MybatisUtils; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import java.util.List; public class UserDaoTest { @Test public void test(){ //1.获取SqlSession对象 SqlSession sqlSession = MybatisUtils.getSqlSession(); //方式一:getMapper UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList = mapper.getUserList(); //方式二:老方法不推荐 // List<User> userList = sqlSession.selectList("com.volcano.dao.UserDao.getUserList"); for (User user : userList) { System.out.println(user); } //最好是放在finally中关闭 sqlSession.close(); } @Test public void testSelect(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.getUserById(1); System.out.println(user); sqlSession.close(); } @Test public void testInsert(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); //传入true设置默认提交事务为true,就不用在下面手动sqlSession.commit()了 //SqlSession sqlSession = MybatisUtils.getSqlSession(true); UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.insertUser(new User(4,"Jack Chen","666666")); //提交事务才会生效 sqlSession.commit(); sqlSession.close(); } @Test public void testDelete(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.deleteUser(3); sqlSession.commit(); sqlSession.close(); } @Test public void testUpdate(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.updateUser(2); sqlSession.commit(); sqlSession.close(); } }
可能遇到的问题
-
标签不要匹配错
-
mybatis-config.xml中要绑定mapper
-
项目运行打包时,xml文件没包含其中,修改pom.xml中的配置,可以放在主工程的pom.xml中
<build> <resources> <resource> <!-- 允许src/main/resources包括includes节点中的文件类型 --> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources> </build>
Map的使用
当传递的参数是一个实体类对象时,若实体类的属性少,则实例化方便,若实体类的属性很多,实例化一个对象很复杂,因为构造方法需要很多参数,然而有些方法并不需要这么多参数。
-
UserMapper.java
//添加用户(Map) int insertUserMap(Map map); -
UserMapper.xml
<!-- 参数类型为map的话,sql语句中的参数可以自定义,但是需要和传入的map中的key一一对应 --> <insert id="insertUserMap" parameterType="map"> insert into mybatis.user values (#{uid},#{uname},#{pwd}) </insert> -
TestUserDao.java
@Test public void testInsertMap(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); HashMap<String, Object> map = new HashMap<String, Object>(); //map中的key与xml文件中的占位符一一对应 map.put("uid",5); map.put("uname","Map5"); map.put("pwd","555555"); mapper.insertUserMap(map); sqlSession.commit(); sqlSession.close(); }
配置解析
- mybatis-config.xml,可以不取这个名字(官方推荐)
- 配置结构(==必须按照顺序配置==)
- configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- environment(环境变量)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
- configuration(配置)
properties
用途:引用外部的配置属性信息。
实例
-
在resources下新建一个db.properties文件
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8 username=root password=123456 -
在mybatis-config.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> <!-- 顺序第一位 --> <!-- 情况1:没有需要额外配置的参数可以直接自闭合 --> <properties resource="db.properties"/> <!-- 情况2:需要额外补充的配置(这边已经把db.properties的password删除)--> <!--<properties resource="db.properties">--> <!--<property name="password" value="1q2w3e4r5t"/>--> <!--</properties>--> <!-- 情况3:额外参数与外部参数重复,外部参数覆盖额外参数 --> <!--<properties resource="db.properties">--> <!-- 如果外部有相同参数,使用外部的 --> <!--<property name="password" value="5555"/>--> <!--</properties>--> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <!-- ${}获取properties文件中的属性 --> <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="com/volcano/mapper/UserMapper.xml"/> </mappers> </configuration>
settings
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。
需要记住以下三个,需要用到别的可以回到官网文档查看:
| 设置名 | 描述 | 有效值 | 默认值 |
|---|---|---|---|
| cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true | false | true |
| lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 | true | false | false |
| logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未设置 |
typeAliases(类型别名)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
<typeAliases>
<!-- XML文件中的com.volcano.pojo.User都可以替换为User -->
<!--<typeAlias type="com.volcano.pojo.User" alias="User"/>-->
<!-- 为整个包下的所有实体类设置别名,别名为类名首字母小写(实测大写也能用) -->
<!-- 这种情况下可以使用注解自定义别名,在实体类上使用@Alias("DIY")设置别名 -->
<package name="com.volcano.pojo"/>
</typeAliases>
//注解指定别名(为整个包指定别名的时候)
@Alias("User")
public class User {
...
}
<!-- UserMapper.xml中的resultType可以指定为别名 -->
<select id="getUserList" resultType="User">
select * from mybatis.user
</select>
environments
MyBatis 可以配置成适应多种环境,例如,开发、测试和生产环境需要有不同的配置。
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
<!-- id指定选择的环境 -->
<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>
<environment id="oracle">
<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>
事务管理器transactionManager
- JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
- MANAGED – 这个配置几乎没做什么。
数据源dataSource
- POOLED——使用数据库连接池
- UNPOOLED——不使用数据库连接池
- JNDI
mappers
映射器,映射自定义的mapper文件。
-
方式一:
<!-- 使用相对于类路径的资源引用 --> <mappers> <mapper resource="org/mybatis/builder/AuthorMapper.xml"/> <mapper resource="org/mybatis/builder/BlogMapper.xml"/> <mapper resource="org/mybatis/builder/PostMapper.xml"/> </mappers> -
方式二:不推荐<!-- 使用完全限定资源定位符(URL) --> <mappers> <mapper url="file:///var/mappers/AuthorMapper.xml"/> <mapper url="file:///var/mappers/BlogMapper.xml"/> <mapper url="file:///var/mappers/PostMapper.xml"/> </mappers> -
方式三:必须要同名.xml在同目录下
<!-- 使用映射器接口实现类的完全限定类名 --> <mappers> <mapper class="org.mybatis.builder.AuthorMapper"/> <mapper class="org.mybatis.builder.BlogMapper"/> <mapper class="org.mybatis.builder.PostMapper"/> </mappers> -
方式四:注意点与方法三相同
<!-- 将包内的映射器接口实现全部注册为映射器 --> <mappers> <package name="org.mybatis.builder"/> </mappers>
拓展:如果想要分离mapper.java和mapper.xml,分别放在java和resources下相同的包名即可,打包后依然在同一目录下。
其他配置
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- MyBatis Generator Core
- MyBatis Plus
- 数据库厂商标识(databaseIdProvider)
- 根据不同的数据库厂商执行不同的语句
生命周期和作用域
-
SqlSessionFactoryBuilder
- 这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了
- 最佳作用域是方法作用域
-
SqlSessionFactory
- 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例
- 应用运行期间不要重复创建多次
- 最佳作用域是应用作用域
- 使用单例模式或者静态单例模式
-
SqlSession
- SqlSession 的实例不是线程安全的,因此是不能被共享的
- 最佳作用域是请求或方法作用域
- 确保每次使用完都要关闭
ResultMap
当数据库表的字段名和实体类中的属性名不一致时,可能会出现查询为null的情况。
id username password user表字段
iden uname pwd User实体类属性
解决方式:
-
修改SQL语句,使用别名,
select id as iden from user; -
在UserMapper.xml使用ResultMap
<!-- id标识 type实体类类名 --> <resultMap id="UserMap" type="User"> <!-- column表的字段名 property实体类的属性名 --> <id column="id" property="iden"/> <id column="username" property="uname"/> <id column="password" property="pwd"/> </resultMap> <!-- resultMap指定resultMap的id --> <select id="getUserById" resultMap="UserMap" parameterType="int"> select * from mybatis.user where id = #{id} </select>
日志
日志可以详细的展现出程序的执行步骤和过程,提高排错能力。
曾经常用的手段:sout、debug等
日志工厂
前面提到Mybatis中settings属性用logImpl可以指定日志的具体实现。name和value一定要避免空格。
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
- SLF4J
- LOG4J【掌握】
- LOG4J2
- JDK_LOGGING
- COMMONS_LOGGING
- STDOUT_LOGGING【掌握】
- NO_LOGGING
STDOUT_LOGGING的日志效果
Log4j
与STDOUT_LOGGING不同的是,Log4j需要导包后才能使用。
- Log4j是Apache的一个开源项目,可以指定日志输出的位置是控制台、文件甚至GUI组件
- 可以控制每一条日志的输出格式
- 定义每一条日志信息的级别
- 这些可以通过一个配置文件来灵活地进行配置
配置
-
导入包
<!-- https://mvnrepository.com/artifact/log4j/log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> -
resources下创建log4j.properties
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码 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/volcano.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 -
配置log4j为Mybatis日志的实现
<settings> <setting name="logImpl" value="LOG4J"/> </settings> -
运行:仅仅多了一些前缀
简单使用
package com.volcano.mapper;
import org.apache.log4j.Logger;
import org.junit.Test;
public class MapperTest {
//获得日志对象,参数为当前类class
//导入的包确定是org.apache.log4j.Logger
static Logger logger = Logger.getLogger(MapperTest.class);
@Test
public void testLog4j(){
logger.info("info:testLog4j");
logger.debug("debug:testLog4j");
logger.error("error:testLog4j");
}
}
[com.volcano.mapper.MapperTest]-info:testLog4j
[com.volcano.mapper.MapperTest]-debug:testLog4j
[com.volcano.mapper.MapperTest]-error:testLog4j
PS:由于在配置文件中配置了log4j.appender.file.File=./log/volcano.log
所以在当前项目根目录下的log文件夹中的volcano.log日志文件中也会追加以上内容
分页
Limit实现
-
UserMapper.java
List<User> getUsersByLimit(Map<String,Integer> limit); -
UserMapper.xml
<!-- map为Mybatis为Map内置的别名 --> <select id="getUsersByLimit" parameterType="map" resultMap="UserMap"> select * from mybatis.user limit #{start},#{pageSize} </select> -
MapperTest.java
@Test public void testLimit(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); Map<String, Integer> map = new HashMap<String, Integer>(); map.put("start",0); map.put("pageSize",2); List<User> userList = mapper.getUsersByLimit(map); for (User user : userList) { System.out.println(user); } sqlSession.close(); }
RowBounds实现
Limit是基于Sql语句的实现,RowBounds是基于Java面向对象的实现,效率不如Sql高,不推荐使用,可能会用于维护老项目。
RowBounds说白了就是,查询所有的数据,然后截断。
-
UserMapper.java
List<User> getUserByRowBounds(); -
UserMapper.xml
<select id="getUserByRowBounds" resultMap="UserMap"> select * from mybatis.user </select> -
MapperTest.java
@Test public void testRowBounds(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); RowBounds rowBounds = new RowBounds(1, 2); //看到这么长的代码就领悟一点不推荐这种方法的原因 List<User> userList = sqlSession.selectList("com.volcano.mapper.UserMapper.getUserByRowBounds", null, rowBounds); for (User user : userList) { System.out.println(user); } sqlSession.close(); }
分页插件
使用注解开发
-
UserMapper.java
@Select("select * from user") List<User> getUserList(); //通过id和name查询用户,多个参数使用注解Param,单个可加可不加(建议加上) //基本类型和String类型可以使用Param,引用类型不用 @Select("select * from user where id = #{uid} and username = #{uname}") User getUserByIdAndName(@Param("uid") int id,@Param("uname") String name); -
mybatis-config.xml
<mappers> <!-- 指定class而不是xml --> <mapper class="com.volcano.mapper.UserMapper"/> </mappers> -
TestMapper.java
@Test public void testAnnotation(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList = mapper.getUserList(); for (User user : userList) { System.out.println(user); } User user = mapper.getUserByIdAndName(1, "volcano"); System.out.println(user); sqlSession.close(); }
缺点:无法用于复杂sql查询,比如返回的类型为对象,注解中无法使用ResultMap。
开发原则:简单查询使用注解,复杂查询依旧使用XML文件配置。在mybatis-config.xml中mappers可以同时配置xml和class
本质:通过反射实现
底层:动态代理
Lombok
利用注解来简化创建实体类的操作的工具。
-
IDEA安装Lombok插件,有则不必(按提示重启IDEA)
-
导入jar包(这里使用Maven)
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.16</version> </dependency> -
开始使用,在实体类上添加@Data注解
常用注解
//所有注解
@Getter
@Setter
@FieldNameConstants
@ToString
@EqualsAndHashCode
@AllArgsConstructor
@RequiredArgsConstructor
@NoArgsConstructor
@Log
@Log4j
@Log4j2
@Slf4j
@XSlf4j
@CommonsLog
@JBossLog
@Flogger
@CustomLog
@Data
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
@val
@var
experimental @var
@UtilityClass
常用的有:
- @Data
- 添加在实体类上,自动生成所有属性的getter和setter、无参构造器、equals()、toString()、hashCode()
- @Getter/@Setter
- 对实体类使用,生成类中所有属性的getter/setter
- 对某个属性使用,生成该属性的getter/setter
- @ToString/@EqualsAndHashCode
- 生成对应方法
总结
少用,团队用就可以用,自己用无所谓。
复杂查询结果处理
之前的查询都是简单查询,实体类中的属性都是String或者基本类型,这些只用resultMap中的result或id即可完成结果映射,而在项目中经常遇到,一个对象的属性是另一个对象,这时候的映射根据情况就需要association和collection。
这里复杂查询我们分为,一对多和多对一。
环境搭建
实体关系:师生关系
- 老师——>学生(一对多)
- 学生——>老师(多对一)
数据库设计
-- 切记分批运行,执行速度过快会导致,数据表没创建完就开始插入数据
CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO teacher(`id`, `name`) VALUES (1, '秦老师');
CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');
项目结构
这些文件中的源码之前笔记都有,这里就放一下实体类的代码
//篇幅原因这里省去构造器和getter/setter,实际项目有
public class Student {
private int id;
private String name;
//这里teacher是一个需要查询的实体类
private Teacher teacher;
}
未处理的复杂查询
这里以多对一示例,通过学生查询对应的老师。
-
StudentMapper.java
List<Student> getStudentList0(); -
StudentMapper.xml
<select id="getStudentList0" resultType="com.volcano.pojo.Student"> select * from mybatis.student </select> -
测试
@Test public void test01(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); List<Student> studentList = mapper.getStudentList0(); for (Student student : studentList) { System.out.println(student); } sqlSession.close(); } -
结果
多对一
出现以上问题,是因为没有用ResultMap进行正确的映射。
-
StudentMapper.java
List<Student> getStudentList1(); List<Student> getStudentList2(); -
StudentMapper.xml
<!-- 按照查询嵌套 --> <select id="getStudentList1" resultMap="StudentAndTeacher"> select * from mybatis.student </select> <!-- 查询返回的结果本就是Student --> <resultMap id="StudentAndTeacher" type="com.volcano.pojo.Student"> <result column="id" property="id"/> <result column="name" property="name"/> <!-- association用多对一 --> <!-- 这里相当于用了子查询 --> <association column="tid" property="teacher" javaType="com.volcano.pojo.Teacher" select="getTeacher"/> </resultMap> <!-- 子查询 --> <select id="getTeacher" resultType="com.volcano.pojo.Teacher"> select * from mybatis.teacher where id = #{tid} </select><!-- 按照结果嵌套 --> <!-- 使用sql查询出结果,用别名作为列名 sid sname tid tname --> <select id="getStudentList2" resultMap="StudentAndTeacher2"> select s.id sid, s.name sname, t.id tid,t.name tname from mybatis.student s, mybatis.teacher t where s.tid=t.id </select> <resultMap id="StudentAndTeacher2" type="com.volcano.pojo.Student"> <result column="sid" property="id"/> <result column="sname" property="name"/> <!-- 把tid tname映射到Teacher --> <association property="teacher" javaType="com.volcano.pojo.Teacher"> <result column="tid" property="id"/> <result column="tname" property="name"/> </association> </resultMap> -
测试(结果相同)
一对多
新建项目(module),除了Student和Teacher实体类变化,XXXMapper.java/xml清空,其余不变。
public class Student {
private int id;
private String name;
//改为tid
private int tid;
}
public class Teacher {
private int id;
private String name;
//一对多个学生
private List<Student> students;
}
-
TeacherMapper.java
Teacher getTeacherById(@Param("tid") int id); Teacher getTeacherById2(@Param("tid") int id); -
TeacherMapper.xml
<!-- 按查询嵌套 --> <select id="getTeacherById" resultMap="TeacherAndStudents"> select * from mybatis.teacher where id = #{tid} </select> <resultMap id="TeacherAndStudents" type="com.volcano.pojo.Teacher"> <result column="id" property="id"/> <result column="name" property="name"/> <!-- 一对多使用collection column把自己的id传入子查询 --> <!-- ofType为List<E>中的E也就是泛型对象 --> <collection column="id" javaType="List" property="students" ofType="Student" select="getStudentByTeacher"/> </resultMap> <select id="getStudentByTeacher" resultType="Student"> select * from mybatis.student where tid = #{id} </select><!-- 按结果嵌套 --> <select id="getTeacherById2" resultMap="TeacherAndStudents2"> select t.id tid, t.name tname, s.id sid, s.name sname,s.tid stid from mybatis.teacher t,mybatis.student s where s.tid = t.id and t.id = #{tid} </select> <resultMap id="TeacherAndStudents2" type="com.volcano.pojo.Teacher"> <result column="tid" property="id"/> <result column="tname" property="name"/> <collection property="students" ofType="com.volcano.pojo.Student"> <result column="sid" property="id"/> <result column="sname" property="name"/> <result column="stid" property="tid"/> </collection> </resultMap> -
测试(结果相同,单行过长,文本表示)
Teacher{id=1, name='秦老师', students=[Student{id=1, name='小明', tid=1}, Student{id=2, name='小红', tid=1}, Student{id=3, name='小张', tid=1}, Student{id=4, name='小李', tid=1}, Student{id=5, name='小王', tid=1}]}
总结
- 多对一
- association
- 一对多
- collection
- javaType:真正返回的数据类型
- ofType:泛型
- 更推荐用按结果嵌套
- 优化SQL是重点
动态SQL
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
所谓的动态SQL,本质还是SQL语句,只是我们可以在SQL层面,去执行一个逻辑代码。
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
环境搭建
-
数据库表
CREATE TABLE `blog`( id varchar(50) NOT NULL COMMENT'博客id', title varchar(100) NOT NULL COMMENT '博客标题', author varchar(30) NOT NULL COMMENT '博客作者', create_time datetime NOT NULL COMMENT '创建时间', views int(30) NOT NULL COMMENT '浏览量' )ENGINE=InnoDB DEFAULT CHARSET=utf8 -
项目结构
-
IDUtils.java
import java.util.UUID; public class IDUtils { public static String getId(){ //通用唯一识别码(Universally Unique Identifier) return UUID.randomUUID().toString().replace("-",""); } } -
Blog.java
public class Blog { private String id; private String title; private String author; //在mybatis-config.xml设置驼峰命名映射mapUnderscoreToCamelCase //Date都用java.util下的 private Date createTime; private int views; } -
BlogMapper.java
void insertBlog(Blog blog); -
BlogMapper.xml
<insert id="insertBlog" parameterType="com.volcano.pojo.Blog"> insert into mybatis.blog(id,title,author,create_time,views) values (#{id},#{title},#{author},#{createTime},#{views}) </insert> -
MyTest.java,自己多插入几条数据
@Test public void test01(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); //插入30条数据 for (int i=0;i<30;i++){ mapper.insertBlog(new Blog(IDUtils.getId(),"标题"+i,"volcano_"+i/10,new Date(),i*20)); } sqlSession.commit(); sqlSession.close(); }
if
可以用if来判断需不需要拼接Sql语句。
-
BlogMapper.java
List<Blog> getBlogIf(Map map); -
BlogMapper.xml
<select id="getBlogIf" parameterType="map" resultType="blog"> -- where 1=1 保证无条件的时候能进行下去,有条件则能顺利拼接 select * from mybatis.blog where 1=1 -- 根据map中是否有title键值对来判断是否拼接 <if test="title != null"> and title = #{title} </if> <if test="author != null"> and author = #{author} </if> </select> -
测试
@Test public void test02(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); //map存放条件,为空查找所有 HashMap map = new HashMap(); map.put("author","volcano_1"); List<Blog> blogList = mapper.getBlogIf(map); for (Blog blog : blogList) { System.out.println(blog); } sqlSession.close(); }
where
在IF的示例中,为了成功拼接条件,在条件起始部分加了一句where 1=1,其实这么写是不安全的,但是不写拼接后又会发生如下情况:
SELECT * FROM BLOG
WHERE
SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’
MyBatis 有一个简单且适合大多数场景的解决办法。而在其他场景中,可以对其进行自定义以符合需求。
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
-
改造
<select id="getBlogIf" parameterType="map" resultType="blog"> select * from mybatis.blog <where> <if test="title != null"> title = #{title} </if> <if test="author != null"> and author = #{author} </if> </where> </select>
choose、when、otherwise
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。(自带break的switch)
choose——switch
when——case
otherw——default
-
BlogMapper.java
List<Blog> getBlogChoose(Map map); -
BlogMapper.xml
<select id="getBlogChoose" parameterType="map" resultType="blog"> select * from mybatis.blog <where> <choose> <when test="author != null"> author = #{author} </when> <when test="title != null"> title = #{title} </when> <when test="views != null"> views > #{views} </when> <otherwise> -- 这里的思路是,如果用户啥也没传就搜索,就给一些访问量高的博客 views > 500 </otherwise> </choose> </where> </select> -
测试
trim、set
用于动态更新语句的类似解决方案叫做 set。set元素可以用于动态包含需要更新的列,忽略其它不更新的列。
set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
-
BlogMapper.java
void updateBlog(Map map); -
BlogMapper.xml
<update id="updateBlog" parameterType="map"> update mybatis.blog <set> <if test="title != null"> title = #{title}, </if> <if test="author != null"> author = #{author}, </if> <if test="views != null"> views = #{views}, </if> </set> <where> <choose> <when test="id != null"> id = #{id} </when> <otherwise> 1 = 2 -- 不给id不让修改数据 </otherwise> </choose> </where> </update> -
测试
@Test public void test04(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); //map存放条件,修改id符合的数据,不给id不会修改 HashMap map = new HashMap(); map.put("id","eb7cc29b63164cf0922ded8c4e67a924"); map.put("author","vip_9999"); map.put("title","标题1——vip专享小尾巴"); mapper.updateBlog(map); sqlSession.commit(); sqlSession.close(); }
其实这里Mybatis中的where和set,都可以用trim元素设置属性实现相同效果。
<!-- where的等价实现 -->
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
<!-- set的等价实现 -->
<trim prefix="SET" suffixOverrides=",">
...
</trim>
<!--
prefix:前缀
prefixOverrides:删除前缀
suffixOverrides:删除后缀
-->
foreach
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。
-
BlogMapper.java
List<Blog> getBlogInAuthors(Map map); -
BlogMapper.xml
<select id="getBlogInAuthors" parameterType="map" resultType="com.volcano.pojo.Blog"> select * from mybatis.blog <where> <foreach collection="authors" open="author in (" separator="," close=")" item="author" index="index"> #{author} </foreach> </where> </select> -
测试
@Test public void test05(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); ArrayList<String> authors = new ArrayList<String>(); authors.add("volcano_1"); HashMap map = new HashMap(); map.put("authors",authors); List<Blog> blogList = mapper.getBlogInAuthors(map); for (Blog blog : blogList) { System.out.println(blog); } sqlSession.close(); }
SQL片段
说白了就是把可以复用的SQL语句抽取出来,作为公共部分,哪里需要哪里搬。
-
抽取公共部分 sql
<sql id="if-title-author"> <if test="title != null"> title = #{title}, </if> <if test="author != null"> author = #{author}, </if> </sql> -
引用 include
<update id="updateBlog" parameterType="map"> update mybatis.blog <set> <include refid="if-title-author"/> <if test="views != null"> views = #{views}, </if> </set> <where> <choose> <when test="id != null"> id = #{id} </when> <otherwise> 1 = 2 </otherwise> </choose> </where> </update>
缓存
简介
-
什么是缓存 [Cache ]?
- 存在内存中的临时数据。
- 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题
-
为什么使用缓存?
- 减少和数据库的交互次数,减少系统开销,提高系统效率。
-
什么样的数据能使用缓存?
- 经常查询并且不经常改变的数据。
Mybatis缓存
MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
-
默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存,生命周期:getSession()—close())
-
二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
-
为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
一级缓存
一级缓存也叫本地缓存:
- SqlSession与数据库同一次会话期间查询到的数据会放在本地缓存中。
- 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库。
示例
@Test
public void testCache01(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
//两次获取相同数据
Blog blog = mapper.getBlogById("eb7cc29b63164cf0922ded8c4e67a924");
System.out.println(blog);
blog = mapper.getBlogById("eb7cc29b63164cf0922ded8c4e67a924");
System.out.println(blog);
sqlSession.close();
}
手动清除缓存
@Test
public void testCache01(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
//两次获取相同数据
Blog blog = mapper.getBlogById("eb7cc29b63164cf0922ded8c4e67a924");
System.out.println(blog);
sqlSession.clearCache();
blog = mapper.getBlogById("eb7cc29b63164cf0922ded8c4e67a924");
System.out.println(blog);
sqlSession.close();
}
二级缓存
- 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
- 基于namespace级别的缓存,一个名称空间,对应一个二级缓存
- 工作机制
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
- 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的
- 数据被保存到二级缓存中
- 新的会话查询信息,就可以从二级缓存中获取内容
- 不同的mapper查出的数据会放在自己对应的缓存(map)中
示例
-
显式开启全局缓存cacheEnabled,默认开启,这里只是显式让开发者看到
<settings> ... <setting name="cacheEnabled" value="true"/> </settings> -
在需要二级缓存的mapper.xml中开启,可以配置缓存策略、时间等参数,参考官网
<cache/> <!-- 如果实体类未实现Serializable 需要加上readonly="true" --> -
测试(切记Sql语句要在XML里配置,用注解不会被缓存!!!)
@Test public void testCache02(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); Blog blog = mapper.getBlogById("eb7cc29b63164cf0922ded8c4e67a924"); System.out.println(blog); //第一个sqlSession关闭后,第二个还能使用数据,说明二级缓存有效果 sqlSession.close(); System.out.println("================="); SqlSession sqlSession2 =MybatisUtils.getSqlSession(); BlogMapper mapper2 = sqlSession2.getMapper(BlogMapper.class); Blog blog2 = mapper2.getBlogById("eb7cc29b63164cf0922ded8c4e67a924"); System.out.println(blog2); //判断是否为同一个引用 System.out.println(blog==blog2); sqlSession2.close(); } -
结果
缓存流程
自定义缓存
EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是一种广泛使用的开源Java分布式缓存。
- 导入包
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
- 在mapper.xml
<cache
type="org.mybatis.caches.ehcache"
/>
- resources下新建配置文件ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<!-- 磁盘缓存位置 -->
<diskStore path="java.io.tmpdir/ehcache"/>
<!-- 默认缓存 -->
<defaultCache
maxEntriesLocalHeap="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxEntriesLocalDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</defaultCache>
<!-- helloworld缓存 -->
<cache name="HelloWorldCache"
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="5"
timeToLiveSeconds="5"
overflowToDisk="false"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
- 解释
diskStore : ehcache支持内存和磁盘两种存储
path :指定磁盘存储的位置
defaultCache : 默认的缓存
maxEntriesLocalHeap=“10000”
eternal=“false”
timeToIdleSeconds=“120”
timeToLiveSeconds=“120”
maxEntriesLocalDisk=“10000000”
diskExpiryThreadIntervalSeconds=“120”
memoryStoreEvictionPolicy=“LRU”
cache :自定的缓存,当自定的配置不满足实际情况时可以通过自定义(可以包含多个cache节点)
name : 缓存的名称,可以通过指定名称获取指定的某个Cache对象
maxElementsInMemory :内存中允许存储的最大的元素个数,0代表无限个
clearOnFlush:内存数量最大时是否清除。
eternal :设置缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期。根据存储数据的不同,例如一些静态不变的数据如省市区等可以设置为永不过时
timeToIdleSeconds : 设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds :缓存数据的生存时间(TTL),也就是一个元素从构建到消亡的最大时间间隔值,这只能在元素不是永久驻留时有效,如果该值是0就意味着元素可以停顿无穷长的时间。
overflowToDisk :内存不足时,是否启用磁盘缓存。
maxEntriesLocalDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
maxElementsOnDisk:硬盘最大缓存个数。
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
diskPersistent:是否在VM重启时存储硬盘的缓存数据。默认值是false。
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
ehcache和Redis
ehcache直接在jvm虚拟机中缓存,速度快,效率高;但是缓存共享麻烦,集群分布式应用不方便。
redis是通过socket访问到缓存服务,效率比Ehcache低,比数据库要快很多,处理集群和分布式缓存方便,有成熟的方案。如果是单个应用或者对缓存访问要求很高的应用,用ehcache。如果是大型系统,存在缓存共享、分布式部署、缓存内容很大的,建议用redis。
ehcache也有缓存共享方案,不过是通过RMI或者Jgroup多播方式进行广播缓存通知更新,缓存共享复杂,维护不方便;简单的共享可以,但是涉及到缓存恢复,大数据缓存,则不合适。
自定义缓存
只要实现Mybatis的cache接口,就可以在<cache type=""/>中指定自定义缓存,实现部分过于高级,不好说。