MyBatis
一个封装好的增强JDBC,是一个轻量级框架,功能简单易学,SQL于java编码分离:SQL是开发人员控制。
通过配置文件和SqlSessionFactory这个重量级资源对SQL进行处理编译。
只需要掌握好sql语句,将sql语句配置在配置文件中即可
MyBatis做到了Sql和代码的分离,提高了代码的可维护性,降低了耦合,简化开发
所有的框架目的都是为了对项目的解耦合,和简化开发。
优点:
- 简单易学
- 灵活
- Sql和代码分离,提高可维护性
- 提供映射标签,支持对象与数据库的orm字段关系映射
- 提供对象关系映射标签,支持对象关系组建维护
- 提供xml标签,支持编写动态sql
MyBatis的下载
Https://github.com/mybatis/mybatis-3/
mybatis原名是iBatis是apache基金会的一个开源项目,2010年由apache software foundation迁移到google code,并改名为mybatis,2013年迁移至github
1. 第一个mybatis程序
-
pom.xml文件添加依赖
<!--mybatis依赖--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> <!--mysql依赖--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.11</version> </dependency>
-
定义mybatis主文件,建议使用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"> <!--mysql驱动--> <property name="driver" value="${driver}"/> <!--mysql链接路径 mysql8.0后需要添加serverTimezone=Asia/Shanghai或者UTC设置编码--> <property name="url" value="${url}"/> <!--数据库用户名--> <property name="username" value="${username}"/> <!--数据库密码--> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="org/mybatis/example/BlogMapper.xml"/> </mappers> </configuration>
environments:末尾是s代表可以添加多个environment子标签environments中的default绑定使用的数据库
-
添加实体类,实体类数据建议和数据库列一致
public class Student{ private int id; private String name; private int age; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", age='" + age + '\'' + '}'; } public Student(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } public Student() { } }
-
添加mybatis的接口对象,接口中定义类一个类对应一个mapper中的子标签
public interface SqlMethods { List<Student> selectStudent(); Student selectStudent1(int id); List<Student> selectStudent2(String name); int insertStudent(Student student); int updateStudent(Map<String,Object> map); int deleteStudent(String name); }
-
定义mapper.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="org.study.mybatis.dao.SqlMethods"> <!--查询--> <select id="selectStudent" resultType="org.study.mybatis.dao.Student"> select id,name,age from t_student </select> </mapper>
namespace:绑定接口类
id:值对应接口的方法,一个标签对应一个方法
resultType:设置标签返回值类型,对应接口类中的方法返回值
parameterType:参数类型,对应接口类中的方法参数
-
编写mybatis工具类
String resource = "org/mybatis/example/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
这是三条固定的语句,同时因为SqlSessionFactory是mybatis中的重量级资源,耗费较大内存,因此,可以将固定的语句提取出来整合成一个工具类,需要时直接调用
//mybatis工具类 public class MybatisUtil { private static SqlSessionFactory sqlSessionFactory; static { String resource = "Mybatis-config.xml"; InputStream inputStream = null; try { inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); } }
注意,SqlSessionFactory是一个重量级资源,一个应用程序只允许出现一次,因此将其定义为static让其跟随应用程序的生成而生成,一直保持存在状态
定义getSqlSession方法是为了方便主类的调用。
-
定义主类
主类实现对数据的增删改查操作
SqlSession sqlSession = MybatisUtil.getSqlSession();
调用mybatis工具类返回一个SqlSession
select
//第一种方法
SqlMethods mapper = sqlSession.getMapper(SqlMethods.class);
List<Student> students = mapper.selectStudent();
for (Student student : students) { System.out.println("id="+student.getId()+"\nname="+student.getName()+"\nage="+student.getAge());
}
//第二种方法
List<Student> list = new ArrayList<>();
List<Student> students = sqlSession.selectList("org.study.mybatis.dao.SqlMethods.selectStudent", list);
for (Student student : students) {
System.out.println(student);
}
<!--查询-->
<select id="selectStudent" resultType="org.study.mybatis.dao.Student">
select id,name,age from t_student
</select>
mybatis中提供了多种select的查询方式
insert
//第一种方式
Student student = new Student();
student.setId(3);
student.setName("秦先生");
student.setAge(24);
String sqlId="org.study.mybatis.dao.SqlMethods.insertStudent";
int insert = sqlSession.insert(sqlId, student);
sqlSession.commit();
System.out.println(insert);
sqlSession.close();
//第二种方式
SqlMethods mapper = sqlSession.getMapper(SqlMethods.class);
int insert = mapper.insertStudent(new Student(3, "郑先生", 21));
sqlSession.commit();
System.out.println(insert);
sqlSession.close();
<!--添加-->
<insert id="insertStudent" parameterType="org.study.mybatis.dao.Student">
insert into t_student(id,name,age) values(#{id},#{name},#{age})
</insert>
update
//第一种方式
Student student1 = new Student();
student1.setId(3);
student1.setName("秦先生");
Map<String,Object> map = new HashMap<>(2);
map.put("name","秦先生");
map.put("id",3);
int update = sqlSession.update("org.study.mybatis.dao.SqlMethods.updateStudent", map);
System.out.println(update);
sqlSession.commit();
sqlSession.close();
//第二种方式
SqlMethods mapper = sqlSession.getMapper(SqlMethods.class);
Map<String,Object> map = new HashMap<>();
map.put("name","李四");
map.put("id",2);
int i = mapper.updateStudent(map);
sqlSession.commit();
sqlSession.close();
System.out.println(i);
<!--修改-->
<update id="updateStudent" parameterType="map">
update t_student set name=#{name} where id=#{id}
</update>
delete
//第一种方式
SqlMethods mapper = sqlSession.getMapper(SqlMethods.class);
int i = mapper.deleteStudent("李四");
sqlSession.commit();
System.out.println(i);
sqlSession.close();
//第二种方式
int delete = sqlSession.delete("org.study.mybatis.dao.SqlMethods.deleteStudent", "张三");
System.out.println(delete);
sqlSession.commit();
sqlSession.close();
```xml
delete from t_student where name=#{name} ```
insert,update,delete这几个都得记得commit和close,不然数据不会提交,mybatis默认是手动提交的。
mybatis中的map集合是个很重要的东西,在mybatis中,可以说是万能的,map会感觉你相匹配的需要植入的sql语句的#{???}部分,map是采用键值对的方式Map<String,int>
就像这样,然后使用map中的put方法添加后,在方法执行时会根据键自动匹配相应的#{???},将值注入进去相应的sql语句中相应的位置
同时,参数类型paramentType="map"
参数类型要改成map
实体类或数据库表,字段或者参数过多的时候,可以考虑使用map或者注解
Map传递参数,直接在sql中取出key即可!
对象传递参数,直接在sQl中取对象的属性即可!
只有一个基本类型参数的情况下,可以直接在sq中取到!
MyBatis中的模糊查询
select id="selectLike" parameterType="string" resultType="org.study.mybatis.dao.Student">
select * from t_student where name like #{name}
</select>
SqlMethods mapper = sqlSession.getMapper(SqlMethods.class);
List<Student> student = mapper.selectLike("%林%");
for (Student student1 : student) {
System.out.println(student);
}
mapper文件中定义模糊查询的sql语句,将模糊查询的数据替换成#{???}(包含通配符% %)
然后再调用相应的方法中给予模糊查询的参数(包含通配符% %)
也可以再sql拼接中使用通配符
select * from t_student where name like "%"#{name}"%"
2. 配置文件优化
1. 核心配置文件
- mybatis-config.xml
- MyBatis的配置文件包含了会深深影响MyBatis行为的设置和属性信息
- 下面图片显示的是mybatis-config.xml中可配置的顶层标签:
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
2. 环境配置(environments)
MyBatis 可以配置成适应多种环境
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
每个数据库对应一个 SqlSessionFactory 实例
事务管理器(transactionManager)
在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):
-
JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
-
MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。
如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。
数据源(dataSource)
dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。
- 大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。
有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]")
详见mybatis3官网内容,数据较多copy麻烦
https://mybatis.org/mybatis-3/zh/configuration.html#environments
UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。
POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。
JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。
3. 属性(properties)
我们可以通过properties属性来实现引用配置文件
定义一个db.properties配置文件,保存数据库连接
mysql.driver=com.mysql.cj.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/student?serverTimezone=UTC&useSSL=false
mysql.username=root
mysql.password=123456
在mybatis主配置文件mybatis-config.xml中的configuration标签添加子标签
<properties resource="db.properties">
<!--除定义db.properties配置文件也可以直接在properties标签内写-->
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
两种方式同时定义的话,优先读取properties元素体内指定的信息,然后根据定义的resource属性读取类路径下的配置文件,覆盖内部的配置信息
引入db.properties配置文件,修改原先的配置文件中关于数据库连接的配置为
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${mysql.driver}"/>
<property name="url" value="${mysql.url}"/>
<property name="username" value="${mysql.username}"/>
<property name="password" value="${mysql.password}"/>
</dataSource>
</environment>
</environments>
${???}中的?对应db.properties配置文件中相应的名称
记住,xml中标签的定义是有顺序的,如下图:
4. 类型别名(typeAliases)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<typeAlias alias="Comment" type="domain.blog.Comment"/>
<typeAlias alias="Post" type="domain.blog.Post"/>
<typeAlias alias="Section" type="domain.blog.Section"/>
<typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>
当你使用typeAliases定义了别名后,可以在mybatis-config.xml主配置文件中定义,然后可以在mapper.xml文件中使用你定义的别名
也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:
<typeAliases>
<package name="domain.blog"/>
</typeAliases>
当你指定一个包名时,会自动在包下搜索需要的类。
每一个在包 domain.blog
中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author
的别名为 author
;若有注解,则别名为其注解值。见下面的例子:
@Alias("author")
public class Author {
...
}
常见别名
下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。
别名 | 映射的类型 |
---|---|
_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 | Object |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
5. 设置(settings)
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true | false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 | true | false | false |
aggressiveLazyLoading | 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods )。 | true | false | false (在 3.4.1 及之前的版本中默认为 true) |
multipleResultSetsEnabled | 是否允许单个语句返回多结果集(需要数据库驱动支持)。 | true | false | true |
useColumnLabel | 使用列标签代替列名。实际表现依赖于数据库驱动,具体可参考数据库驱动的相关文档,或通过对比测试来观察。 | true | false | true |
useGeneratedKeys | 允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。 | true | false | False |
autoMappingBehavior | 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。 | NONE, PARTIAL, FULL | PARTIAL |
autoMappingUnknownColumnBehavior | 指定发现自动映射目标未知列(或未知属性类型)的行为。NONE : 不做任何反应WARNING : 输出警告日志('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志等级必须设置为 WARN )FAILING : 映射失败 (抛出 SqlSessionException ) | NONE, WARNING, FAILING | NONE |
defaultExecutorType | 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(PreparedStatement); BATCH 执行器不仅重用语句还会执行批量更新。 | SIMPLE REUSE BATCH | SIMPLE |
defaultStatementTimeout | 设置超时时间,它决定数据库驱动等待数据库响应的秒数。 | 任意正整数 | 未设置 (null) |
defaultFetchSize | 为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。 | 任意正整数 | 未设置 (null) |
defaultResultSetType | 指定语句默认的滚动策略。(新增于 3.5.2) | FORWARD_ONLY | SCROLL_SENSITIVE | SCROLL_INSENSITIVE | DEFAULT(等同于未设置) | 未设置 (null) |
safeRowBoundsEnabled | 是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。 | true | false | False |
safeResultHandlerEnabled | 是否允许在嵌套语句中使用结果处理器(ResultHandler)。如果允许使用则设置为 false。 | true | false | True |
mapUnderscoreToCamelCase | 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 | true | false | False |
localCacheScope | MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。 | SESSION | STATEMENT | SESSION |
jdbcTypeForNull | 当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。 某些数据库驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。 | JdbcType 常量,常用值:NULL、VARCHAR 或 OTHER。 | OTHER |
lazyLoadTriggerMethods | 指定对象的哪些方法触发一次延迟加载。 | 用逗号分隔的方法列表。 | equals,clone,hashCode,toString |
defaultScriptingLanguage | 指定动态 SQL 生成使用的默认脚本语言。 | 一个类型别名或全限定类名。 | org.apache.ibatis.scripting.xmltags.XMLLanguageDriver |
defaultEnumTypeHandler | 指定 Enum 使用的默认 TypeHandler 。(新增于 3.4.5) | 一个类型别名或全限定类名。 | org.apache.ibatis.type.EnumTypeHandler |
callSettersOnNulls | 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值进行初始化时比较有用。注意基本类型(int、boolean 等)是不能设置成 null 的。 | true | false | false |
returnInstanceForEmptyRow | 当返回行的所有列都是空时,MyBatis默认返回 null 。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集(如集合或关联)。(新增于 3.4.2) | true | false | false |
logPrefix | 指定 MyBatis 增加到日志名称的前缀。 | 任何字符串 | 未设置 |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未设置 |
proxyFactory | 指定 Mybatis 创建可延迟加载对象所用到的代理工具。 | CGLIB | JAVASSIST | JAVASSIST (MyBatis 3.3 以上) |
vfsImpl | 指定 VFS 的实现 | 自定义 VFS 的实现的类全限定名,以逗号分隔。 | 未设置 |
useActualParamName | 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1) | true | false | true |
configurationFactory | 指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为static Configuration getConfiguration() 的方法。(新增于 3.2.3) | 一个类型别名或完全限定类名。 | 未设置 |
shrinkWhitespacesInSql | 从SQL中删除多余的空格字符。请注意,这也会影响SQL中的文字字符串。 (新增于 3.5.5) | true | false | false |
defaultSqlProviderType | Specifies an sql provider class that holds provider method (Since 3.5.6). This class apply to the type (or value ) attribute on sql provider annotation(e.g. @SelectProvider ), when these attribute was omitted. | A type alias or fully qualified class name | Not set |
一个配置完整的 settings 元素的示例如下:
<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>
其中最为重要的是:
-
cacheEnabled,是否开启缓存
-
lazyLoadingEnabled:是否开启懒加载模式
-
mapUnderscoreToCamelCase:是否开启驼峰命名规则,及将数据库列名A_COLUMN转为aColumn
-
useGeneratedKeys:是否运行JDBC支持自动生成主键
-
useCoilmnLabel:使用列标签代替列名
6. 对象工厂(objectFactory)
每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法。 如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现。比如:
// ExampleObjectFactory.java
public class ExampleObjectFactory extends DefaultObjectFactory {
public Object create(Class type) {
return super.create(type);
}
public Object create(Class type, List<Class> constructorArgTypes, List<Object> constructorArgs) {
return super.create(type, constructorArgTypes, constructorArgs);
}
public void setProperties(Properties properties) {
super.setProperties(properties);
}
public <T> boolean isCollection(Class<T> type) {
return Collection.class.isAssignableFrom(type);
}}
<!-- mybatis-config.xml -->
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
<property name="someProperty" value="100"/>
</objectFactory>
ObjectFactory 接口很简单,它包含两个创建实例用的方法,一个是处理默认无参构造方法的,另外一个是处理带参数的构造方法的。 另外,setProperties 方法可以被用来配置 ObjectFactory,在初始化你的 ObjectFactory 实例后, objectFactory 元素体中定义的属性会被传递给 setProperties 方法。
7. 插件(plugins)
plugins插件
- mybatis-generator-core
- mybatis-plus
- 通用mapper
8. 映射器(mappers)
MapperRegistry:注册绑定我们的Mapper文件
既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:///
形式的 URL),或类名和包名等。例如:
推荐方式
<!-- 使用相对于类路径的资源引用 -->
<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>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
注意点:
- 接口和他的Mapper配置文件必须同名
- 接口和他的Mapper配置文件必须在同一个包下
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
注意点:
- 接口和他的Mapper配置文件必须同名
- 接口和他的Mapper配置文件必须在同一个包下
这些配置会告诉 MyBatis 去哪里找映射文件
mapper一般写于dao层里面,一个mapper对应一部分功能
生命周期和作用域
不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。
mybatis的执行流程
mybatis官网中给的对于SqlSessionFactoryBuilder和SqlSessionFactory以及SqlSession的解释是:
SqlSessionFactoryBuilder:
它的存在就是为了创建SqlSessionFactory,一旦SqlSessionFactory被创建就不需要它了,SqlSessionFactoryBuilder可以被实例化,使用和丢弃。可以重复使用SqlSessionFactoryBuilder来创建SqlSessionFactory,但是最好不要保留,以保证所有的 XML 解析资源可以被释放给更重要的事情。
SqlSessionFactory:
可以想象为一个数据库连接池:
SqlSessionFactory应该在程序运行期间一直存在,不要去创建另一个实例,多次创建是一种代码的“坏习惯”。意思就是这个SqlSessionFactory是一个重量级资源,创建会消耗大量的资源,在高并发的情况下很容易造成程序的崩溃,因此我们一个程序创建一个SqlSessionFactory实例即可。
因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
我们需要创建一个类,将SqlSessionFactory定义为一个静态代码块,提供一个方法返回SqlSession即可,当我们需要用到SqlSessionFactory的时候我们只需要调用这个类的方法返回一个SqlSession就可以继续使用
SqlSession:
连接到连接池的一个请求
每个线程都应该有一个SqlSession实例。因为SqlSession是线程不安全的,使用SqlSession是不共享的。使用我们只需要在需要的时候再创建一个SqlSession,当我们获取到返回响应时,我们就可以将SqlSession关闭了。关闭SqlSession这个步骤很重要。
ResultMap
解决属性名和字段名不一致的问题
解决方法:
-
起别名
<select id="" resultType="org.xx.User"/> select </select>
ResultMap
结果集映射
实体类:id name pwd
数据库列:id name password
<!--结果集映射-->
<resultMap id="UserMap" type="User">
<!--id与select标签中的resultMap的值保持一致,type为要映射的实体类-->
<result column="password" property="pwd"/>
<!--column为数据库中的字段名 property是实体类中的属性名-->
</resultMap>
<select id="getUserById" resultMap="UserMap">
select * from t_User where id=#{id}
</select>
-
resultMap
元素是 MyBatis 中最重要最强大的元素。 -
ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
-
ResultMap
的优秀之处——你完全可以不用显式地配置它们。 -
如果世界总是这么简单就好了
mybatis中的ResultMap元素概念图
resultMap
元素有很多子元素和一个值得深入探讨的结构。 下面是resultMap
元素的概念视图。
结果映射(resultMap)
constructor
-用于在实例化类时,注入结果到构造方法中idArg
- ID 参数;标记出作为 ID 的结果可以帮助提高整体性能arg
- 将被注入到构造方法的一个普通结果
id
– 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能result
– 注入到字段或 JavaBean 属性的普通结果association
– 一个复杂类型的关联;许多结果将包装成这种类型- 嵌套结果映射 – 关联可以是
resultMap
元素,或是对其它结果映射的引用
- 嵌套结果映射 – 关联可以是
collection
– 一个复杂类型的集合- 嵌套结果映射 – 集合可以是
resultMap
元素,或是对其它结果映射的引用
- 嵌套结果映射 – 集合可以是
discriminator
– 使用结果值来决定使用哪个resultMap
case
– 基于某些值的结果映射- 嵌套结果映射 –
case
也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射
- 嵌套结果映射 –
属性 | 描述 |
---|---|
id | 当前命名空间中的一个唯一标识,用于标识一个结果映射。 |
type | 类的完全限定名, 或者一个类型别名(关于内置的类型别名,可以参考上面的表格)。 |
autoMapping | 如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。默认值:未设置(unset)。 |
来自mybatis官网的入土套餐
MyBatis 创建时的一个思想是:数据库不可能永远是你所想或所需的那个样子。 我们希望每个数据库都具备良好的第三范式或 BCNF 范式,可惜它们并不都是那样。 如果能有一种数据库映射模式,完美适配所有的应用程序,那就太好了,但可惜也没有。 而 ResultMap 就是 MyBatis 对这个问题的答案。
<!-- 非常复杂的语句 -->
<select id="selectBlogDetails" resultMap="detailedBlogResultMap">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
A.id as author_id,
A.username as author_username,
A.password as author_password,
A.email as author_email,
A.bio as author_bio,
A.favourite_section as author_favourite_section,
P.id as post_id,
P.blog_id as post_blog_id,
P.author_id as post_author_id,
P.created_on as post_created_on,
P.section as post_section,
P.subject as post_subject,
P.draft as draft,
P.body as post_body,
C.id as comment_id,
C.post_id as comment_post_id,
C.name as comment_name,
C.comment as comment_text,
T.id as tag_id,
T.name as tag_name
from Blog B
left outer join Author A on B.author_id = A.id
left outer join Post P on B.id = P.blog_id
left outer join Comment C on P.id = C.post_id
left outer join Post_Tag PT on PT.post_id = P.id
left outer join Tag T on PT.tag_id = T.id
where B.id = #{id}
</select>
你可能想把它映射到一个智能的对象模型,这个对象表示了一篇博客,它由某位作者所写,有很多的博文,每篇博文有零或多条的评论和标签。 我们先来看看下面这个完整的例子,它是一个非常复杂的结果映射(假设作者,博客,博文,评论和标签都是类型别名)。 不用紧张,我们会一步一步地来说明。虽然它看起来令人望而生畏,但其实非常简单。
<!-- 非常复杂的结果映射 -->
<resultMap id="detailedBlogResultMap" type="Blog">
<constructor>
<idArg column="blog_id" javaType="int"/>
</constructor>
<result property="title" column="blog_title"/>
<association property="author" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
<result property="favouriteSection" column="author_favourite_section"/>
</association>
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<association property="author" javaType="Author"/>
<collection property="comments" ofType="Comment">
<id property="id" column="comment_id"/>
</collection>
<collection property="tags" ofType="Tag" >
<id property="id" column="tag_id"/>
</collection>
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost"/>
</discriminator>
</collection>
</resultMap>
3. 日志
Mybatis 通过使用内置的日志工厂提供日志功能。内置日志工厂将会把日志工作委托给下面的实现之一:
- SLF4J
- Apache Commons Logging
- Log4j 2
- Log4j
- JDK logging
MyBatis 内置日志工厂会基于运行时检测信息选择日志委托实现。它会(按上面罗列的顺序)使用第一个查找到的实现。当没有找到这些实现时,将会禁用日志功能。
帮助开发人员排错
在mybatis-config.xml中settings标签中配置
mybatis中使用STDOUT_LOGGING,如果要使用另外的需要在pom.xml中配置
可以去maven仓库中查找相应的插件引入
log4j
- Log4j是Apache的一个开源项目
- 我们可以控制日志信息输送的目的地
- 我们也可以控制每一条日志的输出格式
- 定义每一条日志信息的级别
- 通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
https://mvnrepository.com/
将箭头setting指向的配置复制进maven的pom.xml中
创建一个log4j.properties文件,对log4j进行配置
#配置根
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 = logs/log.log
log4j.appender.file.MaxFileSize = 100mb
log4j.appender.file.Threshold = DEBUG
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern = [%p][%d{yyyy-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
最后一步,在mybatis-config.xml中配置setting
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
简单使用
-
在要使用log4j的类中导入包org.Apache.log4j.Logger;
-
日志对象,参数为当前类的class
public class xxx{ static Logger logger = Logger.getLogger(xxx.class); public static void main(String[] args){ logger.info("info:进入了main"); logger.debug("debug:进入了main"); logger.error("error:进入了main"); } }
4. 分页
使用分页目的
- 减少数据的处理量
使用Limit分页
语法:
SELECT * from t_user limit startIndex,pageSize
如果一页显示十行数据的话
第一页:从0开始,显示十行数据
select * from t_user limit 0,10
第二页,从10开始,显示10行数据
select * from t_user limit 10,10
公式:(页数-1)*显示行数
页数从1开始算起
UserMapper中定义接口:
List<User> getUserByLimit(Map<String,Integer> map);
Mapper.xml
<select id="getUserByLimit" parameterType="map" resultType="User">
select * from t_user limit #{startIndex},#{pageSize}
</select>
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserDao userDao = sqlSession.getMapper(UserMapper.class);
Map<String,Integer> map = new HashMap<>();
map.put("startIndex",0);
map.put("pageSize,10");
List<User> user = userDao.getUserByLimit(map);
for(User user1 : user){
System.out.println(user1);
}
sqlSession.close();
RowBounds分页
不建议开发中使用,了解
不再使用SQL实现分页
-
接口
//分页 List<User> getUserByRowBounds();
-
mapper.xml
<select id="getUserByRowBounds" resultMap="UserMap"> select * from t_user </select>
-
测试
String resource = "org/mybatis/example/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); //RowBounds实现 RowBounds rowBounds = new RowBounds(1,2); //通过java代码层面实现分页 List<User> userList = SqlSession.selectList("com.mybatis.dao.UserMapper.getUserByBounds",null,rowBounds); for(User user :userList){ System.out.println(user); } sqlSession.close();
MyBatis分页插件
https://pagehelper.github.io/
官网文档描述的很清楚
5. 注解开发
1. 面向接口编程
- 根本原因:解耦,可扩展,提高复用性。
- 在分层开发中,上层不用管具体的实现,大家都遵守共同的标准,使得开发更加规范化,简便化。
- 使主要精力放到各对象之间的写作关系上,减少在各种功能对象的内部实现上花过多精力。
1、关于接口的理解
- 接口是定义(规范,约束)与实现的分离
- 接口本身反映了系统设计人员对系统的抽象理解
- 接口有两类:一类是对一个个体的抽象,它可对应一个抽象体(abstract class); 另一类是对一个个体某一方面的抽象,即对应一个抽象面(interface)
- 接口设计与非接口设计是针对复用技术而言的,与面向对象或面向过程不是一个问题,而更多的体现则是对系统整体的架构
2、三个面向区别
- 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法
- 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现
- 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题更多的体现就是对系统整体的架构
就像上学,课本规定了老师要讲的内容,老师可以自由扩展讲课内容丰富知识
例子:
SqlMapper
@Select("select * from t_student")
List<Student> selectStudent();
main:
SqlMethods mapper = sqlSession.getMapper(SqlMethods.class);
List<Student> student = mapper.selectStudent();
for (Student student1 : student) {
System.out.println(student1);
}
将省去在xml文件中定义select标签
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
本质:主要应用反射
底层:动态代理!
MyBatis的详细执行流程
2. 注解CRUD
我们可以在工具类创建的时候实现自动提交事务
sqlSessionFactory.openSession(true);
设置事务自动提交
之前的例子中有提过,insert,update,delete这三个语法执行后是需要调用sqlSession.commit()
方法的,原因就是因为mybatis默认是手动提交事务,因此才需要在sql执行完后调用commit()
方法让事务提交
在openSession()
中将参数设置为true可以将事务提交设置为自动提交
注解参数
//select
@Select("select * from t_student where id=#{id} and name=#{name}")
List<Student> selectLike(@Param("id") int uid,@Param("name")String username);
}
@Insert("insert into t_student(id,name,age) values(#{id},#{name},#{age} )")
int insertStudent(Student student);
@Update("update t_student set age=#{age} where id=#{id}")
int updateStudent(Map<String, Integer> map);
@Delete("delete from t_student where id=#{id}")
int deleteStudent(int id);
方法存在多个参数,所以参数前面必须加上@Param(“value”)
@Param可以将列名不同的参数进行对应
System.out.println("查询");
SqlSession sqlSession = MybatisUtil.getSqlSession();
SqlMethods mapper = sqlSession.getMapper(SqlMethods.class);
List<Student> student = mapper.selectLike(3,"郑先生");
for (Student student1 : student) {
System.out.println(student1);
}
int i = mapper.insertStudent(new Student(6, "秦先生", 23));
System.out.println(i);
Map<String, Integer> map = new HashMap<>();
map.put("age",24);
map.put("id",6);
int i1 = mapper.updateStudent(map);
System.out.println(i1);
int i2 = mapper.deleteStudent(6);
System.out.println(i2);
sqlSession.close();
注意:记得在mybatis-config.xml中绑定接口
<mappers>
<mapper class="org.mybatis.dao.StudentMapper"/>
<!--绑定配置文件-->
<mapper resource="org/mybatis/dao/StudentMapper.xml"
</mappers>
关于@Param()注解
- 基本类型的参数或者String类型,需要加上
- 引用类型不需要加
- 如果只有一个基本类型,可以忽略,建议加上
- 我们在SQL中引用的就是我们这里的@Param(“valuez”)中设定的属性
#{}和${}的区别
#{} 是预编译处理,像传进来的数据会加个" "(#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号)
** ∗ ∗ 就 是 字 符 串 替 换 。 直 接 替 换 掉 占 位 符 。 {}** 就是字符串替换。直接替换掉占位符。 ∗∗就是字符串替换。直接替换掉占位符。方式一般用于传入数据库对象,例如传入表名.
使用 ${} 的话会导致 sql 注入。
所以为了防止 SQL 注入,能用 #{} 的不要去用 ${}
如果非要用 ${} 的话,那要注意防止 SQL 注入问题,可以手动判定传入的变量,进行过滤,一般 SQL 注入会输入很长的一条 SQL 语句
面试题:接口绑定有两种方式
1、使用注解,在接口的方法上面添加@Select@Update等注解,里面写上对应的SQL语句进行SQL语句的绑定。
2、通过映射文件xml方式进行绑定,指定xml映射文件中的namespace对应的接口的全路径名
什么时候用注解绑定?什么时候用xml绑定?
当SQL语句比较简单的时候,使用注解绑定就可以了,当SQL语句比较复杂的话,使用xml方式绑定,一般用xml方式绑定比较多
Lombok
Lombok 项目是一个 java 库,可自动插入编辑器并构建工具,将您的 java 弹出。
永远不要再写另一个获取器或等于方法,用一个注释您的类有一个功能齐全的建设者,自动化您的记录变量,等等。
使用步骤:
-
idea安装Lombok插件
-
导入lombok的jar包
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> <scope>provided</scope> </dependency>
-
使用lombok注解实体类
@Data @AllArgsConstructor @NoArgsConstructor public class Student{ private int id; private String name; private int age; }
复杂查询环境搭建
多对一处理
-
导入环境依赖
<!--mybatis依赖--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> <!--mysql依赖--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.11</version> </dependency> <!--log4j依赖--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <!--lombok依赖--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> <scope>provided</scope> </dependency>
-
新建数据库,创建student和teacher表,将学生表中的学生关联教师id
create table t_teacher( id int(5) primary key, name varchar(5) ); insert into t_teacher(id,name) values(1,"林老师"); create table t_student( id int(5) primary key, name varchar(20), tid int(5), foreign key(tid) references t_teacher(id) ); insert into t_student(id,name,tid) values(1,"郑学生",1); insert into t_student(id,name,tid) values(2,"秦学生",1); insert into t_student(id,name,tid) values(3,"林学生",1); insert into t_student(id,name,tid) values(4,"司马学生",1); insert into t_student(id,name,tid) values(5,"刘学生",1);
-
创建学生和教师实体类
- 教师类(TeacherMapper)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher{
private int id;
private String name;
}
- 学生类(StudentMapper)
```java
@Data
@AllargsConstructor
@AllargsConstructor
public class Student{
private int id;
private String name;
private Teacher teacher;
}
-
创建教师类和学生类的Mapper文件
-
教师Mapper(TeacherMapper)
<?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="org.mybatis.dao.TeacherMapper"> <select id="selectTeacher" resultType="Teacher"> select * from t_teacher </select> </mapper>
-
学生Mapper(StudentMapper)
<?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="org.mybatis.dao.StudentMapper"> <select id="selectStudent" resultType="Student"> select * from t_student where id = #{id} </select> </mapper>
-
-
创建mybatis主配置文件(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> <!--引入第三方配置给${??}赋值--> <properties resource="db.properties"/> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${mysql.driver}"/> <property name="url" value="${mysql.url}"/> <property name="username" value="${mysql.username}"/> <property name="password" value="${mysql.password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="org/mybatis/dao/TeacherMapper.xml"/> <mapper resource="org/mybatis/dao/StudentMapper.xml"/> </mappers> </configuration>
-
创建db.properties
mysql.driver=com.mysql.cj.jdbc.Driver mysql.url=jdbc:mysql://localhost:3306/student?serverTimezone=UTC&useSSL=false mysql.username=root mysql.password=123456
-
创建log4j配置文件
#配置根 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 = logs/log.log log4j.appender.file.MaxFileSize = 100mb log4j.appender.file.Threshold = DEBUG log4j.appender.file.layout = org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern = [%p][%d{yyyy-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
-
创建接口
public interface SqlMethods { Student selectStudent(); Teacher selectTeacher(); }
-
mybatis工具类封装
public class MybatisUtil { private static SqlSessionFactory sqlSessionFactory; static { String resource = "Mybatis-config.xml"; InputStream inputStream = null; try { inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(true); } }
-
测试
SqlSession sqlSession = MybatisUtil.getSqlSession(); SqlMethods mapper = sqlSession.getMapper(SqlMethods.class); Student student = mapper.selectStudent(); Teacher teacher = mapper.selectTeacher(); for(Student student1 : student){ System.out.println(student1); } for(Teacher teacher1 : teacher){ System.out.println(teacher1); } mapper.close();
-
查询出的结果
Student(id=1, name=郑学生, teacher=null)
Student(id=2, name=秦学生, teacher=null)
Student(id=3, name=林学生, teacher=null)
Student(id=4, name=司马学生, teacher=null)
Student(id=5, name=刘学生, teacher=null)
sql中,我们的语句是
select s.id,s.name,t.name from t_student s,t_teacher t where s.tid=t.id
mybaits中,我们之前学习的方法已经不够用了,而mybatis中给我们提供了专门处理复杂查询的方法
association:处理对象的 javaType
collection:处理集合的 ofType
第一种写法:
<select id="selectStudent" resultType="org.study.mybatis.dao.Student">
select s.id,s.name,t.name from t_student s,t_teacher t where s.tid=t.id
</select>
<select id="selectStudent" resultMap="StudentTeacher">
select s.id sid,s.name sname,t.name tname from t_student s,t_teacher t where s.tid=t.id
</select>
<resultMap id="StudentTeacher" type="org.study.mybatis.dao.Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<!--复杂属性需要单独列出处理-->
<!--处理对象-->
<association property="teacher" javaType="org.study.mybatis.dao.Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
第二种写法:
思路:
我们要查询的是两个表联合起来查询的,所以我们在两个表中都有数据
因此我们可以先各自将各自要查询的数据查询出来
然后通过mybatis提供的resultMap标签将两张表联合起来
<select id="selectStudent" resultMap="StudentTeacher">
select * from t_student
</select>
<resultMap id="StudentTeacher" type="org.study.mybatis.dao.Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<association property="teacher" javaType="org.study.mybatis.dao.Teacher" column="tid" select="selectTeacher"/>
</resultMap>
<select id="selectTeacher" resultType="org.study.mybatis.dao.Teacher">
select * from t_teacher where id=#{id}
</select>
以上是属于多对一的处理
回顾mysql的多对一查询方式:
-
子查询
select id,name FROM t_student where tid=(select id from t_teacher where name="林老师");
根据老师姓名查询该名老师的学生有哪些
-
联表查询
SELECT table1.*, table2.* FROM table1, table2; # 等同于内连接查询 SELECT table1.*, table2.* FROM table1 INNER JOIN table2;
左连接:left join: 左外连接(左连接), 以左表为主表
左表不管能不能匹配上条件,最终都会保留:能匹配,正确的保留; 若不能匹配,右表的字段都置NULL。
基本语法:from 左表 left join 右表 on 左表.字段 = 右表.字段;
SELECT s.id, s.name, t.name teacherName FROM t_student s LEFT JOIN t_teacher t ON s.tid=t.id;
右连接:right join: 右外连接(右连接), 以右表为主表
右表不管能不能匹配上条件,最终都会保留:能匹配,正确的保留; 若不能匹配,左表的字段都置NULL。
基本语法: from 左表 right join 右表 on 左表.字段 = 右表.字段;
SELECT s.id, s.name, t.name teacherName FROM t_student s RIGHT JOIN t_teacher t ON s.tid=t.id;
完全外连接:
SELECT s.id, s.name, t.name teacherName FROM t_student s FULL OUTER JOIN t_teacher t ON s.tid=t.id;
一对一处理
一个老师对多个学生,对于老师来说这就是一对多
其余配置如常;变化在于两个实体类
- student实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student{
private int id;
private String name;
private int tid;
}
- teacher实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
private int id;
private String name;
//一个学生拥有多个老师
private List<Student> student;
}
-
接口
Teacher getTeacher(@Param("id")int id);
-
TeacherMapper.xml
<!--这是一种方法 按照结果嵌套处理--> <select id="getTeacher" resultMap="TeacherStudent"> select t.id tid,t.`name` tname,s.`name` sname,s.id sid,s.tid stid from t_teacher t, t_student s where t.id=s.tid and t.id=#{id}; </select> <resultMap id="TeacherStudent" type="org.study.mybatis.dao.Teacher"> <result property="id" column="tid"/> <result property="name" column="tname"/> <collection property="student" ofType="org.study.mybatis.dao.Student"> <result property="id" column="sid"/> <result property="name" column="sname"/> <result property="tid" column="stid"/> </collection> </resultMap>
思路:
-
先将查询出想要查询的老师查询的sql写出来
-
然后再将通过老师id查询该名老师有哪些学生的sql写出来
-
通过resultMap将两条select标签串合起来,所以resultMap标签的type最终是返回给teacher实体类的,所以type要写teacher
collection:是处理集合的
这里使用collection是将查询student的数据存放进去一个集合这个集合会赋值给实体类中的student
select连接下方查询student的标签 column是老师的id也是下面select的参数
<!--这是另一种方法,按照查询嵌套处理--> <select id="getTeacher" resultMap="TeacherStudent"> select id,name from t_teacher where id=#{id} </select> <resultMap id="TeacherStudent" type="org.study.mybatis.dao.Teacher"> <collection property="student" ofType="org.study.mybatis.dao.Student" javaType="ArrayList" select="getStudentById" column="id"/> </resultMap> <select id="getStudentById" resultType="org.study.mybatis.dao.Student"> select * from t_student where tid=#{id} </select>
-
小结
在上面两个例子中我们看到
关联-association 是多对一使用的
集合-collection是一对多使用的
javaType & ofType
- javaType 用来指定实体类中属性的类型
- ofType 用来指定映射到List或者集合中的泛型约束类型
尽量保证SQL的可读性保持通俗易懂
注意一对多和多对一中,属性名和字段的问题
如果问题不好排查,可以使用日志
尽量少使用Lombok,可读性不高,而且是不太好的
面试高频
-
MySql引擎
-
InnoDB底层原理
-
索引
-
索引优化
动态SQL
动态SQL是可以根据需要的不同自动拼接不同的SQL,是MyBatis的一个强大特性,可以简化开发
在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
if
choose (when, otherwise)
trim (where, set)
foreach
搭建环境
-
创建数据库
create database Blog; use Blog; create table t_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;
-
创建基础工程
-
导包
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.11</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> <scope>provided</scope> </dependency>
-
编写配置文件
<?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"/> <settings> <setting name="logImpl" value="STDOUT_LOGGING"/> <setting name="mapUndersoreToCamelCase" value="true"/> <!--是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。--> </settings> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${mysql.driver}"/> <property name="url" value="${mysql.url}"/> <property name="username" value="${mysql.username}"/> <property name="password" value="${mysql.password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="org/study/mybatis/dao/BlogMapper.xml"/> </mappers> </configuration>
mysql.driver=com.mysql.cj.jdbc.Driver mysql.url=jdbc:mysql://localhost:3306/Blog?serverTimezone=UTC&useSSL=false mysql.username=root mysql.password=123456
-
编写实体类
@Data public class Blog{ private int id; private String title; private String author; private Date ctreateTime; private int views; }
-
编写实体类对于Mapper接口和Mapper.xml文件
BolgMapper.java
public interface BolgMapper{ int addBlog(Bolg blog); }
BolgMapper.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="org.mybatis.dao.BlogMapper"> <insert id="addBlog"> insert into t_blog(id,title,author,create_time,views) values(#{id},#{title},#{author},#{create_time},#{views}); </insert> </mapper>
-
工具类
IDUtils,随机生成Blog的id值,replaceAll将生成的-去掉
public class IDUtils{ public static String getId(){ return UUID.randomUUID().toString().replaceAll("-","") } }
MybatisUtils
public class MybatisUtil { private static SqlSessionFactory sqlSessionFactory; static { String resource = "Mybatis-config.xml"; InputStream inputStream = null; try { inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(true); } }
-
测试
SqlSession session = mybatisUtils.getSqlSession(); BolgMapper mapper = session.getMapper(BolgMapper.class); Blog blog = new Blog(); blog.setId(IDUtil.getId()); blog.setTitle("StudyMyBatis"); blog.setAuthor("MR.Zheng"); blog.setCreateTime(new Date()); blog.setViews(9999); int i = mapper.addBlog(blog); session.close();
-
IF
使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分
<select id="findActiveBlogWithTitleLike"
resultType="Blog">
SELECT * FROM t_blog
WHERE author = 'MR.Zheng'
<if test="title != null"><!--test输入条件-->
AND title like #{title}<!--满足条件时增加的部分-->
</if>
</select>
这条语句提供了可选的查找文本功能。如果不传入 “title”,那么所有author= “MR.Zheng” 的数据都会被查找出来
如果传入了title,那么被if包含的语句会拼接前面的语句
select * from t_blog where author = 'MR.Zheng and title like #{title}'
两个参数进行可选搜索该怎么办呢?
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM t_blog WHERE author = 'MR.Zheng'
<if test="title != null">
AND title like #{title}
</if>
<if test="create_time != null">
AND create_time like #{createTime}
</if>
</select>
choose、when、otherwise
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
就是传入哪个参数就由mybatis找到对应的参数标签查询
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM t_blog WHERE author = 'MR.Zheng'
<choose>
<!--如果传入这个就拼接这个-->
<when test="title != null">
AND title like #{title}
</when>
<!--如果传入的是这个就拼接这个-->
<when test="create_time != null">
AND create_time like #{createTime}
</when>
<!--如果上面两个都不符合就拼接这个-->
<otherwise>
or title='Spring'
</otherwise>
</choose>
</select>
when只会拼接一句,它的意思是如果满足则,满足条件后不会再向下执行
trim、where、set
where 标签
如果我们想连上面where后面的author条件一起设置成动态条件
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM t_blog
WHERE
<if test="author != null">
state = #{author}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND create_time like #{createTime}
</if>
</select>
如果第一个条件不满足,满足第二个条件那么拼接会变成select * from t_blog where and title like %Spring%
这样的SQL是错误的
MyBatis 有一个简单且适合大多数场景的解决办法。而在其他场景中,可以对其进行自定义以符合需求。而这,只需要一处简单的改动:
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM t_bloh
<where>
<if test="author != null">
auhtor = #{author}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="create_time != null">
AND create_time like #{createTime}
</if>
</where>
</select>
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
Set
**set:用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。**比如:
<update id="updateAuthorIfNecessary">
update t_blog
<set>
<if test="author != null">author=#{author},</if>
<if test="title != null">title=#{tiele},</if>
<if test="create_time != null">create_time=#{create_time},</if>
<if test="views != null">views=#{views}</if>
</set>
where title=#{title}
</update>
这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
set会根据你传入的数据进行判断,如果你的数据包含其中某个条件,那么set会自动对Update进行拼接,并且会自动删除多余的逗号
update t_blog set author="MR.Zheng" where title="Spring"
trim
trim是与where和set等价的自定义元素,可以代替where和set使用
where:
prefix:翻译是前缀的意思,就是表示你要在前面添加上面。
prefixOverrides:翻译是前缀覆盖,表示要去除你包含的语句的前缀
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
set:
suffixOverrides:翻译就是后缀覆盖的意思,表示你要去除的后缀是什么
<trim prefix="SET" suffixOverrides=",">
...
</trim>
SQL片段
有时候,我们会将公共部分抽取出来,方便重复使用
<!--定义可复用sql,sql公共部分-->
<sql id="if-title-author">
<if test="title!=null">
title=#{title}
</if>
<if test="author!=null">
and author=#{author}
</if>
</sql>
<!--引用可复用sql-->
<select id="selectBlog" parameterType="map" resultType="Blog">
<include refid="if-title-author"/>
</select>
注意事项:
- 最好使用单表来定义SQL片段
- 不要存在where标签
foreach
select * from t_blog where 1=1 and (id=1 or id=2 or id=3);
<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from t_blog
<where>
<!--collection存放一个键为ids的map集合,item是由collection查询出来的id,open是表示从哪里开始,close表示从哪里结束,separator表示分隔符,转换完后会将item的id赋值给#{id}-->
<foreach collection="ids" item="id" open="and(" close=")" separator="or">
id=#{id}
</foreach>
</where>
</select>
接口
List<Blog> queryBlogForeach(Map map);
测试
Sqlsession sqlSession = Mybatis.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1);
Map map = new HashMap();
map.put("ids",ids);
List<Blog> blog = mapper.queryBlogForeach(map);
fo(Blog blog1 : blog){
System.out.println(blog1);
}
sqlSession.close();
动态SQL的本质上还是SQL,只是在SQL的基础上增添了逻辑代码
动态SQL就是在拼接SQL语句,我们只要保证SQl的正确性,按照SQL的格式去排列组合就可以了
缓存
查询:连接数据库,耗资源
一次查询的结果,给他暂存再一个可以直接取到的地方–>内存 :缓存
再次查询相同数据的时候,直接走缓存,就不用耗费资源重新从数据库中查询相关数据
-
什么是缓存[Cache ]?
- 存在内存中的临时数据。
- 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
-
为什么使用缓存?
- 减少和数据库的交 次数,减少系统开销,提高系统效率。
-
什么样的数据能使用缓存?
- 经常查询并且不经常改变的数据。【可以使用缓存】
- 经常改变,不经常用的数据【不能使用缓存】
MyBatis缓存
MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。
MyBatis默认情况下,只启用了本地的会话缓存(一级缓存),它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:<cache/>
二级缓存是基于namespace级别的缓存
为了提高扩展性,MyBatis定义了缓存接口Cache,我们可以通过实现Cache接口来实现自定义二级缓存
基本上就是这样。这个简单语句的效果如下:
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
提示 缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。
可用的清除策略有:
LRU
– 最近最少使用:移除最长时间不被使用的对象。FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象。WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
默认的清除策略是 LRU。
flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
提示 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
一级缓存
测试方法:
- 开启日志
- 在SqlSession中查询2次同样数据
缓存失效情况:
-
查询不同数据
-
增删改查,可能会更改原来的数据,所以缓存会进行刷新
-
查询不同的Mappe.xml
-
手动清除缓存
sqlSession.clearCache();
默认是一级缓存,只在一次sqlSession中有用也就是拿到连接到关闭连接这个区间有用
每个用户来查都会开启一个SqlSession,会频繁操作数据库
一级缓存相当于一个Map
二级缓存
<mapper ...>
<cache/><!--开启二级缓存-->
</mapper>
<cache
eviction="LRU | FIFO | SOFT | WEAK"
flushInterval="60000"
size="1024"
readOnly="true"
/>
<!--eviction:选择清除策略
flushInterval:刷新间隔,毫秒
size:最多存储要缓存的对象,结果或列表的1024个引用,默认值是1024
readOnly:是否只读
-->
mybatis-config.xml
<settings>
<setting name="cacheEnabled" value=""/><!--显示开启全局缓存-->
</settings>
- 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
- 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
- 工作机制
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
- 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
- 新的会话查询信息,就可以从二级缓存中获取内容;
- 不同的mapper查出的数据会放在自己对应的缓存(map)中
<select id="select" resultType="user" useCache="false">
select * from t_user
</select>
二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。
使用二级缓存我们需要将实体类序列化,否则会报错
让实体类去实现Serializable接口
小结:
- 只要开启了二级缓存,在同一个Mapper下有效
- 所有的数据会先放在以及缓存中
- 只有当会话提交,或者关闭的时候,才会提交到二级缓存中
缓存原理
自定义缓存
Ehcache:
EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点
Ehcache是一种广泛使用的开源Java分布式缓存
主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点。
使用ehcache
-
导包
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache --> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.1.0</version> </dependency>
-
配置
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
-
创建并配置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" updateCheck="false"> <!-- diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下: user.home – 用户主目录 user.dir – 用户当前工作目录 java.io.tmpdir – 默认临时文件路径 --> <diskStore path="java.io.tmpdir/Tmp_EhCache"/> <!-- defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。 --> <!-- name:缓存名称。 maxElementsInMemory:缓存最大数目 maxElementsOnDisk:硬盘最大缓存个数。 eternal:对象是否永久有效,一但设置了,timeout将不起作用。 overflowToDisk:是否保存到磁盘,当系统当机时 timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。 timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。 diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false. diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。 diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。 memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。 clearOnFlush:内存数量最大时是否清除。 memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。 FIFO,first in first out,这个是大家最熟的,先进先出。 LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。 LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。 --> <defaultCache eternal="false" maxElementsInMemory="10000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="1800" timeToLiveSeconds="259200" memoryStoreEvictionPolicy="LRU"/> <cache name="cloud_user" eternal="false" maxElementsInMemory="5000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="1800" timeToLiveSeconds="1800" memoryStoreEvictionPolicy="LRU"/> </ehcache> <!--https://www.cnblogs.com/zqyanywn/p/10861103.html-->
http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation=“http://ehcache.org/ehcache.xsd”
updateCheck=“false”>
<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>