所需环境 | 回顾 |
---|---|
JDK1.8 | JDBC |
Mysql5.7/8.0 | Mysql |
maven3.6.1 | Maven |
IDEA | Junit |
SSM(Spring + SpringMvc + Mybatis)框架:配置文件相关的。学习的最好方式:官方文档。
获得Mybatis
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.2version>
dependency>
数据持久化
持久化就是将程序的数据在持久状态(硬盘/外存中)和瞬时状态(内存中)转化的过程
内存(Random Access Memory,RAM):断电即失
在大学里面看过一本关于早期工业革命历史的书,学习到有个叫继电器的器件,早期内存核心就是继电器,具体构造是个弹片,随着电位信息拨动(挨上就是1,没挨上就是0,这也就是为什么计算器底层是二进制的又一证据),这个继电器的电位信息来自于地址总线(几根就是我们说的32/64操作系统,说的就是内存大小),因为直接由地址总线控制,所以能直接定位(Access)某个位置,这也就是为什么内存速度快。同时内存核心元件是继电器,一旦断电,继电器拨片会立马回弹,导致回到初始状态,即数据都变成000000序列了,数据就不复存在了。这就是RAM存储器的特性,速度快,怕断电。在计算机中充当着内存的角色。毕竟是课外学习到的知识,也不是科班,所以可能有误,望高人指出。
本质上都是放在外存上。学习的课外知识。分为固态硬盘和机械硬盘。机械硬盘,你可以看作小时候看的碟机的CD运行原理,有一个小小的指针放在上面随着转动这就是再读取了。所以机械硬盘怕摔,针头偏移了的话,没咬合住就读取不了了。固态硬盘,半导体技术,懵逼,不懂,只知道他有个专业术语是颗粒。
为什么要持久化
持久层:Dao层,Service层,Controller层。
总结及自己的思考
Mybatis框架完全可以不学,你可以用更加全自动的Hibernate框架。Hibernate全自动框架,Mybatis半自动框架。理解就在于hibernate将sql语句都内部处理了(黑箱操作),用户使用时只需调用相关api即可。而mybatis还需自己写sql语句,这样更加灵活。因为不同的需求可能需要不同程度的sql语句,sql这种变化大的由外面来写最好。
还是那句话,框架没有好坏之分,但现在主流的方向就是这个,所以为了迎合,必须要学。
同时,对于框架,我只想说,不用太放在心上,刨根问题,就是人家为我们做好的工具,你可以拿来使用,只不过要学习人家规定的语法/配置文件。你完全可以给一个不懂底层实现的人让他去学框架的使用,使用框架使得开发越来越快。框架就是人家定好的规则,你去学习使用完成自己的目标,做一个API的调用者,很简单;但要做一个框架的开发者(工具的建造者),很难。所以学习框架就是个上山和下山的过程,上山学习如何快速使用,下山,刨根问题,去探究人家这个功能是怎么实现的。深度和广度的问题,取决在于你。
思路:搭建环境–>导入Mybatis–>编写代码–>测试
搭建数据库
CREATE DATABASE `mybatis`;
USE mybatis;
CREATE TABLE `user`(
`id` INT(20) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`pwd` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `user` (`id`,`name`,`pwd`) VALUES
(1,'狂神','123456'),
(2,'张三','456852'),
(3,'李四','qwertyuiop');
新建项目
新建一个普通的maven项目
删除src目录(使用父工程子项目模式来做,为了一呼百应)
父工程pom.xml导入maven依赖
<dependencies>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.26version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.2version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
dependencies>
创建一个子模块,该子模块也是maven的普通项目。
在子模块的resource目录(web学习中明白打包导出后直接就在当前目录下)下编写mybatis-config.xml(官方建议这样命名)。
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.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="456852"/>
dataSource>
environment>
environments>
configuration>
该配置文件不要写中文注释(硬要写UTF-8改为UTF8/GBK)!
&
代替&
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;
//本质上在 sqlSessionFactory 建造 sqlSession
public class MybatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
//使用MybatisUtils第一步:获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream resourceAsStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
// SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
写在对应包下,与数据库表一一对应。
public class UserModel {
private int id;
private String name;
private String pwd;
//get,set,toString来一套
}
public interface IUserDao {
List<UserModel> getUserList();
}
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.dao.IUserDao">
<select id="getUserList" resultType="com.kuang.pojo.UserModel">
SELECT * FROM mybatis.`user`
select>
mapper>
右边感叹警告,未配置数据库。配置Mysql,方便快速查询相应表和字段。
<mappers>
<mapper resource="com/kuang/dao/userDaoMapper.xml"/>
mappers>
因为此时要访问的是一个目录下的xml文件,而不是类文件,所以要用/
,而不用.
。
junit测试,应该去test目录下进行。所以要在test目录下建立和src一样的包结构。
import com.kuang.pojo.UserModel;
import com.kuang.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class userDaoTest {
@Test
public void userDaoTest() {
//第一步:获得SqlSession对象
SqlSession sqlSession = MybatisUtil.getSqlSession();
//执行
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
List<UserModel> userList = userDao.getUserList();
for (UserModel userModel : userList) {
System.out.println(userModel);
}
sqlSession.close();
}
}
可能遇到的问题
要去mybatis的核心配置文件下进行相关xml文件的注册绑定
Mybatis为了增加关联性,我们经常把xml文件等配置文件和类放在一起。但是默认导入包不会导出src下的配置文件。所以在父工程下的pom.xml下加过滤器。
为了关联性:
为了不被过滤,所以需要在pom.xml添加
<build>
<resources>
<resource>
<directory>src/main/resourcesdirectory>
<includes>
<include>**/*.propertiesinclude>
<include>**/*.xmlinclude>
includes>
<filtering>falsefiltering>
resource>
<resource>
<directory>src/main/javadirectory>
<includes>
<include>**/*.propertiesinclude>
<include>**/*.xmlinclude>
includes>
<filtering>falsefiltering>
resource>
resources>
build>
Mybatis就是把我们以前写userDaoImple实现类做法转成了写一个xml文件的做法。让我们以前写的好多代码(connection、preparestatment等)得到简化。
演化过程
以前做法 | Mybatis做法 |
---|---|
implements 接口 | namespace = “包权限接口名” |
实现接口方法 | id=“接口方法名” |
返回值 | resultType = “带包权限类名” |
Mybatis就是把实现类变为写一个xml标签,其标签体专注SQL语句。写完该xml,记得要去mybatis的核心配置文件进行注册。namespace是绑定,去核心配置文件添加叫注册。
有了Mybatis以后的CRUD步骤都是一致的。
编写接口—>编写接口对应的mappeer.xml中的sql语句—>测试(固定四步,打开sqlSession;获取sqlSession.getMapper(接口.class);进行相关操作;最后关闭sqlSession)
Select
选择,查询语句;
//名字命名起的越底层,高层越方便调用以达到复用,DAO和Service想成两个人之间的对话
UserModel getUserRecordById(int id);
<select id="getUserRecordById" parameterType="int" resultType="com.kuang.pojo.UserModel">
SELECT * FROM mybatis.`user` where id = #{id}
select>
@Test
public void selectTest() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
System.out.println(userDao.getUserRecordById(2));
sqlSession.close();
}
Insert
<insert id="addUserRecord" parameterType="com.kuang.pojo.UserModel">
insert into mybatis.user (id, name, pwd) VALUES (#{id},#{name},#{pwd})
insert>
Update
<update id="updateUserRecord" parameterType="com.kuang.pojo.UserModel">
update mybatis.user set name = #{name},pwd = #{pwd} where id = #{id}
update>
Delete
<delete id="deleteUserRecordById" parameterType="int">
delete from mybatis.user where id = #{id}
delete>
需要注意的是:增删改操作必须进行业务提交sqlSession.commit();
期间发现几个问题。
有可能错误的原因
/
,而不用.
。回顾上面写的,修改(update)用户时,不应该传一个user,而应该传递id(改哪个),name、pwd(改成什么);或者考虑这么一个问题,如果一个实体类有100个字段值,你填都要填半天,我们只填自己需要的。
IUserDao
//重名了,测试看可以吗? 不可以,报错,Mapped Statements collection already contains
//map映射已经有一个了,也就是说方法的重载在mybatis是不可行的
//boolean updateUserRecord(Map map);
boolean updateUserRecordNew(Map<String,Object> map);
userDaoMapper.xml
<update id="updateUserRecordNew">
#这里的 updateName updatePwd whichId 必须和塞的时候放的键值一模一样
update mybatis.user set name = #{updateName},pwd = #{updatePwd} where id = #{whichId}
update>
测试
@Test
public void newUpdate() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
Map<String, Object> map = new HashMap<>();
map.put("whichId",0);
map.put("updateName","王小明");
map.put("updatePwd","mzby");
//#这里的 塞的键值updateName updatePwd whichId 必须和xml取得时候保持一致
//否则执行时返回的为0/false,且数据库没发生变化
System.out.println("成功了吗?" + userDao.updateUserRecordNew(map));
sqlSession.commit();
sqlSession.close();
}
因此,建议Mybatis在传递多个参数时,使用map(设置 parameterType=“map”),直接在sql中取出key即可!
可以但不建议传输对象实例,做法(设置parameterType=“带包权限类名”),直接在sql中取出对象的属性即可!
只有一个基本类型参数的情况下,可以自己在sql中取到,且不管形参名字!底层原因好像就是以下标取得的。
多个参数用Map,或者注解!
其他多参传递参考这个博文 即可,但最简单最直观还是建议使用Map(官方也是这么说的)。
经过这次例子,还学到了Mybatis中方法不可以重载,因为你是通过名字绑定的,重名问题解决不了。从这回想起方法重名(重载)的底层原理,我们看到的方法名其实在底层根本不是这个,而是带数字的,形如fun_1()
,fun_2()
之类的,不过程序员这层看它们名字相同。
模糊查找
List<User> userList = mapper.getUserLike("%李%");
select * from mybatis.user where name like "%"#{value}"%"
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
MyBatis 可以配置成适应多种环境。这种机制有助于将 SQL 映射应用于多种数据库(Mysql,Oracle)之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置。
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
学会使用配置多套运行环境
Mybatis默认的事务管理器就是JDBC,连接池:POOlED
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
dataSource>
environment>
我们可以通过properties属性来实现引用配置文件
这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。典型的 Java 属性【db.properties】
db.properties 放在resources目录下,打包后直接在classpath下
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
username=root
password=456852
在核心配置文件中引用
<properties resource="db.properties">
<property name="usernameOne" value="root"/>
<property name="password" value="qwert"/>
properties>
并且,从这个问题从中学到了,XML文件标签是有结构顺序的!!!
可以看到,如果相关XML前后位置写错,IDEA也会报错的。
你如果深入到底层去看XMLConfigBuilder这个类parseConfiguration方法,可以看到它解析就是有顺序的
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
<typeAliases>
<typeAlias type="com.kuang.pojo.User" alias="User"/>
typeAliases>
在实际开发中,工作项目下,还是会使用完全限定名,使得以后一眼就能看明白!
还是都参考官方文档。
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。
最为重要的四个参数,要眼熟。
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true/false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 |
true/false | false |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J LOG4J LOG4J2 JDK_LOGGING COMMONS_LOGGING STDOUT_LOGGING NO_LOGGING | 无 |
mapUnderscoreToCamelCase | 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 | true/false | false |
驼峰转换有时候需开启,我们知道数据库字段名一般都用下划线隔开,因为数据库对大小写不敏感,而且Oracle数据库都是全大写,所以要用下划线隔开,而实体类中的属性名是以驼峰命名法走的,因此两者挂钩需要进行修改。
前面我们说过,当相关接口的实现的mapper.xml(形如userDaoMapper.xml)写出来后,就立刻去核心配置文件注册它。
方式一:【极其推荐使用】万能,写对绝对可用
<mappers>
<mapper resource="com/kuang/dao/userDaoMapper.xml"/>
mappers>
我们知道此时是根据路径去定位相关文件,因此这里用的是/
而不是.
。或者确切的来说,只有Java的目录结构用.
来进行深入访问,而其他所有文件都和操作系统保持一致。
方式二:【有额外要求,不推荐使用】使用class文件绑定注册
<mappers>
<mapper class="com.kuang.dao.UserMapper">mapper>
mappers>
都参考官方文档。
这一节,我想拿出来单独说,因为里面包含的是设计思想。
作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。
可以深入到源码去看,SqlSessionFactoryBuilder最主要都是为外提供了build方法,都是为了得到一个SqlSessionFactory。而这些build方法,其本质都是在解析核心配置XML文件!
可以这样理解:其作用读取XML文件,根据XML(environment标签)实例出不同的SqlSessionFactory,SqlSessionFactoryBuilder就是读取核心配置文件产生SqlSessionFactory,而我们知道有个environment标签,会产生不同的环境,SqlSessionFactoryBuilder就是根据这个来创造SqlSessionFactory的。
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
SqlSessionFactory是一个对应环境(environment标签)的实例,可以理解为数据库连接池,程序运行中一直存在。某个数据库(JDBC/Oracle)的数据库连接池。应该单例,一次程序完整运行,一次环境(某个数据库),随着一次程序的关闭再关闭。单例原因:一次程序运行只能使用一个环境(用一个数据库)。
其源码主要方法都是一个openSession()
,从这可以更加确定的认为其是一个数据库连接池,对外提供数据库连接,可提供多个。
SqlSession可以看作从数据库连接池(SqlSessionFactory)得到的一次连接(具有connection和preparestatment的能力)。线程不安全,用完及时归还。
我们可以看它的源码,SqlSession就是一个接口,其里面含有
void commit();
void rollback();
Connection getConnection();
<T> T getMapper(Class<T> type); //mybatis最主要用这个
从上述看来,它就是Connection和Preparestatment的集合体。
Java采用驼峰命名原则,而数据采用下划线分割命名原则,这两肯定会有不一致的情况。
例如,如下情况。
数据库中的字段
实体类
public class UserModel {
private int id;
private String name;
private String password;
}
测试出现问题
仔细查询,明白问题所在,在相关mapper的xml文件中的SQL语句
select * from mybatis.user where id = #{id}
//同等于
select id,name,pwd from mybatis.user where id = #{id}
*
是选中数据库的所有字段名,所以选的是pwd,而此时实体类属性名为password,两者不一致,所以导致没查出来(后面分析很明显这是使用了反射机制)。
select id,name,pwd as password from mybatis.user where id = #{id}
只需要构造sql语句时将它的别名和属性名一致即可。分析其原因:其实就是sql执行后会建立一张新表,这个表的名字就是按上面走的,然后这时候就一致了(就方便反射了)。
这种解决方式太low了,取巧方式,如果有很多字段和属性不一致就要大量增加。Mybatis有自己的解决方式。
属性名:id name password
字段名:id name pwd
在相关mapper的XML里将这种映射关系描述出来,在一个mapper标签里里表述出来
<mapper namespace="com.kuang.dao.IUserDao">
<resultMap id="resultMapOne" type="com.kuang.pojo.UserModel">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="password" column="pwd"/>
resultMap>
<select id="getUserList" resultMap="resultMapOne">
select * from mybatis.user
select>
mapper>
其实相同的可以删除。可以将id和name的映射删除也不影响结果。
说实话看到这个resultMap,我有很亲切的感觉,我做过相关的工作(详见自主实现简易的ORM框架)。这个resultMap这种机制我亲手实现过,其追其根本就是反射机制!
当我看到mybatis和我以前的做法一样时,不禁狂喜,但是应该怎么测出来呢,得出它使用了反射机制这个结论呢?是个博弈猜测对话的过程,在此分享出来。
一开始,我就把实体类的所有gettersetter方法全部删除,我没有反射执行的方法,我看你怎么反射。
(1)删除所有getsetter,没有我看你怎么反射
public class UserModel {
private int id;
private String name;
private String password;
}
但是,结果还是出来了。从这可以分析出,mybatis框架的设计者考虑到有这种情况,没有相关set方法就自己创建相关set方法。佩服!那继续吃我下一招。
(2)getseter设置为private,定义了设置为私有的我看你怎么反射
很遗憾的是,这次mybatis框架设计者又赢了。依然可以执行出结果。从这明白了,反射存在setAccessable这种黑科技,priavte修饰符在这都没用。
(3)实属无奈,最后想到小黄鸭调试法
直接给setgettet方法里面加输出语句,看他是否输出,从此判断是否使用了反射机制。
public class UserModel {
private int id;
private String name;
private String password;
public int getId() {
return id;
}
public void setId(int id) {
System.out.println("setId反射执行了");
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("setName反射执行了");
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
System.out.println("setPassword反射执行了");
this.password = password;
}
}
答案是令人满意的,确实输出了,也直接证明了resultMap是使用反射机制实现的,和以前的我做的ORM框架类似。
这只是resultMap的基本用法,用来映射简单的字段和属性。但我们知道一个数据库一定是复杂的,类的属性也不简单的是八大基本类型,肯定有类类型。所以resultMap后面引入了association
等其他标签,这就是resultMap的高级用法了。但是,我只想究其本质的说,根本不需要怕什么高级用法,这个XML的resultMap的标签只是在描述表和类之间的不同的部分,心中有了映像图,不论是写还是读,你都可以很快明白!association
就是描述来自于其他表的字段和属性之间的映射关系,追溯其里面的标签还是在描述字段和属性的映射关系,我们此时就可以和我做的框架一样把这种映射关系提取出来形成一个类PropertyColumnDefintion,这就是我的ORM框架的做法了,我不知道Mybatis底层是怎样做的,但我认为我这方便理解面向对象思想。
总结:mybatis对于属性和字段名不一样有两种解法。这两者分别就是数据库层面上和Java层面上,对于数据库层面上的修改可以通过别名解决,而在Java层面上就要借助反射机制了。
同时,追求解答的过程是有趣并痛苦的。从这可以看出来Mybatis框架鲁棒性是多么的强啊!膜拜!
如果数据库操作,出现异常,我们需要排错。日志就是最好的助手!
曾经:sout(小黄鸭测试法)、debug
现在:日志工厂。
在Mybatis中具体使用哪一个日志实现,在设置中设定!
STDOUT_LOGGING 标准日志输出
在mybatis核心配置文件中,配置日志!
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
settings>
Log4j
什么是Log4j
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和fiLe的定义在下面的代码
1og4j.rootLogger=DEBUG, console,file
#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
1og4j.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
1og4j.appender.file.File=./log/kuang.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.sq1=DEBUG
1og4j.logger.java.sq1.statement=DEBUG
log4j.logger.java.sq1.ResultSet=DEBUG
log4j.logger.java.sq1.PreparedStatement=DEBUG
3 . 配置log4j为日志的实现
<settings>
<setting name="logImpl" value="LOG4J"/>
settings>
Log4j类中使用
在要使用Log4j的类中,导入包import org.apache.log4j.Logger;
日志对象,参数为当前类的class
static Logger logger = Logger.getLogger(UserDaoTest.class);
logger.info("info:进入了testLog4j");
logger.debug("debug:进入了testLog4j");
logger.error("error:进入了testLog4j");
在日志学习这里遇到找不到相关资源问题,经过网上搜索解决了,并且深刻的理解,打包后的项目都发生了什么?
首先,一个Mavne项目的目录结构从src开始分析,分为两大部分main和test。main下的java(蓝色标注):该目录下是我们写的.java后缀结尾的源代码,resources(黄色金币标注):该目录下是我们项目所需要的xml、properties文件。test下就是测试的东西,需要程序员自己进行和main形成一个镜像。
一旦Maven项目运行起来了,它就进行了打包。main和test分别形成了classes和test-classes目录。classes目录展开来看和java目录有着一模一样的结构,文件也都一样只不过名称变成了.class的字节码文件。我们以后为了保护源码传给别人的就是打包后的classes文件;同时,还发现并没有resources目录了,而原本resources目录下的所有文件直接附着在了classes文件下,这就是为什么我们经过打包后,每次访问资源文件都不需要加路径,直接进行访问,因为程序当前就在classes目录下待着,它此时就是以这里为根据,访问其他相对位置的地方,这个初始的路径我们有个重要的称谓classPath,classPath就是我们程序员编写代码前最开始的路径,也是打包后的class路径,resources目录随着打包自动合并了。Test也是如此。
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
//这就是为什么我们直接可以访问资源文件前不需要加任何路径的原因,因为当前就在classPath下
//以后所有去访问所有路径,都站在该classPath进行相对定位
Thread.currentThread().getContextClassLoader().getResource("")
//而这句话,根据classLoad就能得到当前程序运行的绝对路径(带盘符的那种),就知道当前在哪个物理上真真实实的路径了
所以我们,以后只提classPath这个概念。普通Java程序的src就是classPath,web项目的src也是classPath,只要开始编代码的都是classPath,resources目录随着打包自动合并,其他路径的文件根据当前classPath进行相对定位!
思考:为什么要分页?
分页SQL语句 limit
select * from mybatis.user limit startIndex,pageSize;
#从第几个下标(startIndex)开始,显示几个(pageSize)
select * from mybatis.user limit 3;
#[0,3]
使用Mybatis实现分页,核心SQL
1.接口
List<UserModel> getUserListLimit(Map<String, Integer> paraMap);
2.绑定一个mapper.xml
<mapper namespace="com.kuang.dao.IUserDaoFour">
<resultMap id="getUserListLimitPropertyColumnMapping" type="com.kuang.pojo.UserModel">
<result property="password" column="pwd"/>
resultMap>
<select id="getUserListLimit" parameterType="map" resultMap="getUserListLimitPropertyColumnMapping">
select * from mybatis.user limit #{startIndex},#{pageSize}
select>
mapper>
3.测试
public void test() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
Map<String,Integer> paraMap = new HashMap<>();
paraMap.put("startIndex", 0);
paraMap.put("pageSize", 3);
IUserDaoFour iUserDaoFour = sqlSession.getMapper(IUserDaoFour.class);
List<UserModel> userListLimit = iUserDaoFour.getUserListLimit(paraMap);
for (UserModel userModel : userListLimit) {
System.out.println(userModel);
}
sqlSession.commit();
sqlSession.close();
}
RowBounds分页
过时了,不建议学习。
分页插件
pageHelper
-大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程
-根本原因∶解耦,可拓展,提高复用,分层开发中,上层不用管具体的实现,大家都遵守共同的标准,使得开发变得容易,规范性更好
-在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;
-而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。
关于接口的理解
-接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。
-接口的本身反映了系统设计人员对系统的抽象理解。
-接口应有两类:
-第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);-
-第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface) ;-
-一个体有可能有多个抽象面。抽象体与抽象面是有区别的。
三个面向区别
-面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法.
-面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现.
-接口设计与非接口设计是针对复用技术而言的,与面向对象〈过程)不是一个问题.更多的体现就是对系统整体的架构
我理解的面向接口编程:接口是由富有经验的架构师设计的,一个优秀的架构师可以设计十分实用且复用次数多的框架。面向接口编程在工程上就是为了省钱,加快开发效率。试问有了Java之后,你给一个学校开发一套系统,完成了然后再给另一个学校开发他们的系统,难道还需要从头开始吗?这是不必要的,因此有了框架的概念,框架相当于人的骨头,肉随着骨头去长,也就是说其他锦上添花的需求不应该在框架内考虑,框架只做主体系统的内部结构。框架按层结构来搭建,Dao层只干Dao层的事,Service只做自己该干的事。这一层的接口只做自己接口的事。
对接口有了更深刻的思考,即插即用,我只是定个方向,具体怎么实现接口不管,你给我注入不同的实现类,该接口就可以进行复用了。
@Select("select * from mybatis.user")
List<UserModel> getUserList();
<mappers>
<mapper class="com.kuang.dao.IUserDaoFour"/>
mappers>
本质:反射机制和动态代理机制
编写接口,增加注解
@Select("select * from user")
List<User> getUsers();
//方法存在多个参数,所有的参数前面必须加@Param("id")注解
@Select("select * from user where id = ${id}")
User getUserById(@Param("id") int id);
@Insert("insert into user(id,name,pwd) values (#{id},#{name},#{password})")
int addUser(User user);
@Update("update user set name=#{name},pwd=#{password} where id = #{id}")
int updateUser(User user);
测试类
【注意,必须将接口注册绑定到核心配置文件中】
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
IUserDaoFour mapper = sqlSession.getMapper(UserMapper.class);
mapper.updateUser(new User(98, "sino", "abcdef"));
sqlSession.commit();//不加,数据库不会有
sqlSession.close();
}
关于@Param("")注解
#{}(防止SQL注入), ${}(可以拼接,导致SQL注入)
没有进行学习,getsetter写起来很快的,没必要让别人帮你做。同时getsetter才是一个面向对象的灵魂(封装思想)所在!而且,如果你想要自己千奇百怪的pojo输出格式,就需要自己写toString方法,别什么都依赖别人帮你写。
ER图
多对一:
数据库图
所需要的数据库:
CREATE TABLE `teacher`(
`id` INT(20) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `teacher`(`id`,`name`) VALUES
(1,'朱老师');
INSERT INTO `teacher`(`id`,`name`) VALUES
(2,'狂老师');
CREATE TABLE `student`(
`id` INT(20) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`teacher_id` INT(10) DEFAULT NULL,
PRIMARY KEY(`id`),
KEY `fktid` (`teacher_id`),
CONSTRAINT `fktid` FOREIGN KEY (`teacher_id`) REFERENCES `teacher` (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `student`(`id`,`name`,`teacher_id`) VALUES(1,'小明',1);
INSERT INTO `student`(`id`,`name`,`teacher_id`) VALUES(2,'小红',2);
INSERT INTO `student`(`id`,`name`,`teacher_id`) VALUES(3,'小张',1);
INSERT INTO `student`(`id`,`name`,`teacher_id`) VALUES(4,'小李',2);
INSERT INTO `student`(`id`,`name`,`teacher_id`) VALUES(5,'小王',1);
建议一步步执行,否则会出现表还没建立,就先插入数据导致错误了。
工程上还是建议不要建立外键,这里只是为了举例子才设置外键。
建立实体类
TeacherModel
public class TeacherModel {
private int id;
private String name;
//getset toString来一套
}
StudentModel
public class StudentModel {
private int id;
private String name;
//private int teacherId; 考虑再三,不需要
private TeacherModel teacherModel;
//get set toString
}
这个teacherId,到底在不在StudenModel里有以下的考虑。
teacherId存在:
优点:以后得到一个学生的实例(studentmodel),可以直接根据它蕴藏的tid查到它的老师(后面发现好像teacher就表示了,teacherId真的没有存在必要了,立刻删)
缺点:数据冗余,teacherId与teacher表达意思一样,数据冗余。
在java的世界里,teacher就是teacher,它也是个实体类,我根本不管你在数据库的编号是啥,我只只知道student有teacher这个属性。所以 int teacherId没必要。 或者是,可以写这个teacherId,不过不对外显示,get方法包权限,set方法包权限,只在内部用,对外不显示。
从这明白了,java是java的世界,数据库是数据库的世界,我们俩要的不一样,表示也就不一样。
建立接口
public interface IStudentDao {
List<StudentModel> getStudentList();
}
建立接口的实现XML文件
StudentDaoMapper.xml
<mapper namespace="com.kuang.dao.IStudentDao">
<select id="getStudentList" resultMap="getStudentListPropertyColumnMapping">
select s.id as sid,s.name as sname,s.teacher_id as tid,t.name as tname
from student as s,teacher as t where s.teacher_id = t.id
select>
<resultMap id="getStudentListPropertyColumnMapping" type="com.kuang.pojo.StudentModel">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacherModel" javaType="com.kuang.pojo.TeacherModel">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
association>
resultMap>
mapper>
这里最重要的就是写SQL语句了,在这我也彻底明白了SQL语句,下面总结下。
select语句主体
SELECT (查出来形成新表的列名,强烈建议用as写好新表的列名) FROM (从哪几张表,建议也用as起别名,方便区分是哪一个表的列) WHERE (条件,不加就会变成笛卡尔积的形式)
写Select语句就先写主体,SELECT … FROM … WHERE,然后根据需求去填充。(别名用as明示,别只写个空格)
select s.id as sid,s.name as sname,s.teacher_id as tid,t.name as tname
from student as s,teacher as t where s.teacher_id = t.id
联表查询的SQL语句就很好写和分析了。上面的SQL语句代表从student表(别名为s)和teacher表(别名为t),形成结果新表sid列是student表的id;新表sname列是student表的name;新表tid列是student表的teacher_id;新表tname是teacher表的name;条件为student表的teacher_id与teacher表的id相等。
起别名的原因是:经过select查询形成的新表的新列名就是按照别名走的,如果没设置就按源表走;同时设置了别名,mybtatis写参数也就按照新表走了。
在mybatis核心配置XML文件进行注册
<mappers>
<mapper resource="com/kuang/dao/StudentDaoMapper.xml"/>
mappers>
测试,成功。
StudentDaoMapper.xml
<select id="getStudentListOne" resultMap="getStudentListOnePropertyColumnMapping">
select * from student
# 直接查全表
select>
<resultMap id="getStudentListOnePropertyColumnMapping" type="com.kuang.pojo.StudentModel">
<association property="teacherModel" column="teacher_id" javaType="com.kuang.pojo.TeacherModel" select="exSelect"/>
resultMap>
<select id="exSelect" resultType="com.kuang.pojo.TeacherModel">
select * from teacher where id = #{id}
select>
在mybatis核心配置XML文件进行注册;测试,也成功。
但还是建议使用连表查询,构造SQL简单且易懂。
连表查询构建的SQL直接可以在数据库的控制台进行输入,然后看结果,而子查询不行,子查询还牵扯参数传递,所以复杂不好理解和调试。
比如:一个老师拥有多个学生!
对于老师而言,就是一对多的关系
实体类
public class TeacherModel {
private int id;
private String name;
private List<StudentModel> studentList;
}
接口
public interface ITeacherDao {
TeacherModel getTeacherById(@Param("teacherId") int id);
}
建立接口的实现XML文件
TeacherDaoMapper.xml
<mapper namespace="com.kuang.dao.ITeacherDao">
<select id="getTeacherById" resultMap="getTeacherByIdPropertyColumnMapping">
SELECT s.id AS sid, s.name AS sname, t.name AS tname, t.id AS tid
FROM student AS s, teacher AS t
WHERE s.teacher_id = t.id AND t.id = #{teacherId}
select>
<resultMap id="getTeacherByIdPropertyColumnMapping" type="com.kuang.pojo.TeacherModel">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<collection property="studentList" ofType="com.kuang.pojo.StudentModel">
<result property="id" column="sid">result>
<result property="name" column="sname">result>
collection>
resultMap>
mapper>
去mybatis核心配置文件进行注册,测试ok。
只写了连表查询,子查询就不写了,难度大且不易懂。
注意点:
慢SQL 1s 1000s的差距 学习了sql索引知识后,把我们现在写的sql放在一边,参照着写。
什么动态SQL:动态SQL就是指根据不同的条件生成不同的的SQL语句
if
<select id="queryBlogIF" parameterType="map" resultType="Blog">
select *
from mybatis.blog where 1=1
<if test="title != null">
and title = #{title}
if>
<if test="author != null">
and author = #{author}
if>
select>
where 1 = 1这个是为了where语句必须出现,但不知道后面是否满足条件。因此写个1 = 1。1=1 永真, 1<>1 永假。where 1<>1 这句查询出来的是 只要表结构不要表数据。
choose (when, otherwise)形如switch语句
<select id="queryBlogIF" parameterType="map" resultType="Blog">
select * from mybatis.blog
<where>
<choose>
<when test="title != null">
title = #{title}
when>
<when test="author != null">
and author = #{author}
when>
<otherwise>
and views = #{views}
otherwise>
choose>
where>
select>
这里没有再使用where 1 = 1了,而使用了where标签。where标签,可以让SQL字符串拼接正确。
有了where标签就不建议使用where 1=1了。where 1=1会导致表中的数据索引失效。
所谓的动态SQL,本质还是SQL语句,只是可以在SQL层面,去执行一个逻辑代码
SQL片段和foreach操作就不涉及了。
怎么说呢,我认为理解并且会写最简单if就行了,动态SQL就是在拼接SQL字符串,mybatis就是操作数据库的框架,按照分离原则,数据库只管数据库的操作,少引入逻辑操作,引入个if就不错了。没有动态SQL,你可以一直划分细颗粒度的DAO操作,写的越简单越基础的方法,粒度越小,复用性越高,可以称为元操作吧。
缓存(Cache,kashi,以前一直读错kachi纠正),这是一个很重要的概念。缓存对mybatis优化很起作用,缓存说白了就是提高查询效率。
最开始,单服务器,少量用户向服务器进行请求资源,服务器只负责和客户连接,资源文件肯定不存在服务器上,而放在数据库上,服务器分析客户的请求,然后从数据库取得相关数据再返还,早期就是这样的单线;
而随着网络越来越发达,用户数量激增,传统的单个服务器承受不了巨大的压力,因此就需要多个服务器来进行处理,这些服务器可以跨地域设置以满足不同地区用户的体验感,快速响应用户请求。
虽说请求由于多个服务器所以处理速度快了,但是真正的资源要去数据库拿;而数据库其本质也是个服务器,其最主要工作就是读和写。压力有都转移到数据库这里了。应该怎么办呢?架构中没有什么是加一层解决不了的,分析读操作和写操作应该是独立的,所以加一层memoryCache缓存,memoryCache存储常用的数据库资源,实现读写分离,读操作先读memoryCache读,没有再去数据库读,而写的压力全给数据库。
如果还嫌处理速度不够快,那么就把数据库也弄成多个,这样可以分担压力。但是多个数据库就要考虑数据一致性的问题了。如何保证呢?主从复制!
从上面发展历史中,你也就能明白缓存就是一个存放常用数据的仓库(池子),方便用户快速得到。
Mybatis中的缓存也是如此意思
1.什么是缓存[ Cache ]
2.为什么使用缓存?
3.什么样的数据能使用缓存?
mybatis一级缓存又称本地缓存。
一级缓存默认打开
一级缓存生命周期是一次会话(一次打开和关闭sqlSession),此会话期间有效
public void test() {
SqlSession sqlSeeion = MybatisUtil.getSqlSeeion(); //openSession
//一级缓存仅在此之间有效
sqlSeeion.commit();
sqlSeeion.close();
}
(1)与数据库同一次会话期间查询到的数据会放在本地缓存中。
(2)以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;
@Test
public void test() {
SqlSession sqlSeeion = MybatisUtil.getSqlSeeion();
IUserDaoFive mapper = sqlSeeion.getMapper(IUserDaoFive.class);
UserModelFive user = mapper.getUserById(1);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
UserModelFive anotherUser = mapper.getUserById(1);
System.out.println("他俩地址值相同吗?" + (user == anotherUser)); //true
sqlSeeion.commit();
sqlSeeion.close();
}
输出为true,查询同一个id,一开始我以为底层肯定是使用反射机制new了一个新的pojo对象,而此时==为true,说明地址值相同,就说明此时这两个对象是一样的,这就证明了一级缓存的存在。同时日志信息可以看到相关SQL只执行了一次,说明第二次对象就是从一级缓存取的。
(1)增删改(insert,update,delete)操作,可能会改变原来的数据,为了确保数据实时,所以会刷新一级缓存
(2)查询不同的Mapper.xml(也就是说查了另一个接口的实现XML文件,从这知道同一个sqlsession下的不同mapper的缓存是各自的,后证实为不同的mapper.xml对应不同的二级缓存,后面学到可以mapper.xml通过引用传递二级缓存)
@Test
public void testMapperOneCache() {
SqlSession sqlSeeion = MybatisUtil.getSqlSeeion();
IUserDaoFive mapperOne = sqlSeeion.getMapper(IUserDaoFive.class);
UserModelFive userOne = mapperOne.getUserById(1);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
IUserDaoSix mapperTwo = sqlSeeion.getMapper(IUserDaoSix.class);
UserModelFive userTwo = mapperTwo.getUserByUserId(1);
System.out.println("地址值相同吗?" + (userOne == userTwo));
//false。说明不同的mapper.xml有自己不同的一级缓存(后证实为不同的mapper.xml对应不同的二级缓存)
sqlSeeion.commit();
sqlSeeion.close();
}
(3)手动清理缓存
sqlSeeion.clearCache();
使用步骤
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="logImpl" value="STDOUT_LOGGING"/>
settings>
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
所有的数据都会先放在一级缓存中,只有会话提交(sqlSeeion.commit()),或者关闭(sqlSeeion.close())的时候,才会转存到二级缓存中!
开启上面两个条件后,二级缓存就生效了。当一级缓存关闭(sqlsession进行close后)时,同一mapper.xml下查询的对象会放在二级缓存中;不同的mapper.xml有自己独有的二级缓存。
@Test
public void testTwoCache() {
SqlSession sqlSeeion = MybatisUtil.getSqlSeeion();
IUserDaoFive mapper = sqlSeeion.getMapper(IUserDaoFive.class);
UserModelFive user = mapper.getUserById(1);
sqlSeeion.commit();
sqlSeeion.close();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
SqlSession sqlSeeion1 = MybatisUtil.getSqlSeeion();
IUserDaoFive mapper1 = sqlSeeion1.getMapper(IUserDaoFive.class);
UserModelFive user1 = mapper1.getUserById(1);
sqlSeeion1.commit();
sqlSeeion1.close();
System.out.println("地址值相同吗?" + (user == user1));
//true
//1.核心文件开启显示开启缓存
//2.mapper.xml定义缓存
//cache没有readOnly="true",会报未序列化异常,和流有关系
}
@Test
public void testMapperMoveStore() {
SqlSession sqlSeeion = MybatisUtil.getSqlSeeion();
IUserDaoFive mapper = sqlSeeion.getMapper(IUserDaoFive.class);
UserModelFive user = mapper.getUserById(1);
sqlSeeion.commit();
sqlSeeion.close();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
SqlSession sqlSeeion1 = MybatisUtil.getSqlSeeion();
IUserDaoSix mapper1 = sqlSeeion1.getMapper(IUserDaoSix.class);
UserModelFive user1 = mapper1.getUserByUserId(1);
sqlSeeion1.commit();
sqlSeeion1.close();
System.out.println("不同mapper的二级缓存可以引用吗?" + (user == user1));
//true。探究过程很有意思
}
<cache-ref namespace="com.kuang.dao.IUserDaoFive"/>
这里试了半天都是false,心态都崩了,和官方文档说的cache-ref不一样啊。在崩溃之际,网上搜索资料,终于解开谜题。参考了这位大佬的博文cache-ref。从中明白了cache是一个实例对象,不能既引入
,还引入<cache-ref/>
;解析
结点,Mapper为自己创建一个缓存实例不再引用<cache-ref/>
的缓存实例;也就是说一个mapper要么定义自己的二级缓存,要么引用别人的二级缓存,只能用一个池子。
缓存原理映像图
当然可以使用自己自定义的缓存,我们先来看org.apache.ibatis.cache.Cache
接口。
package org.apache.ibatis.cache;
import java.util.concurrent.locks.ReadWriteLock;
public interface Cache {
String getId();
void putObject(Object key, Object value);
Object getObject(Object key);
Object removeObject(Object key);
void clear();
int getSize();
default ReadWriteLock getReadWriteLock() {
return null;
}
}
你要实现自定义缓存就要实现该接口,完成其规定的所有方法。
从这里明白了。实际上,设计一个接口,提出这些方法就是设计一种策略!规定你必须完成什么,后面的实现该接口的实现类是策略的具体实现了。
然后相关mapper.xml导入自定义缓存就行了
<cache type="com.domain.something.MyCache"/>
工作一般在Redis(已经做到极致了)数据库来做缓存! K-V键值对的形式。都以K-V存储,直接取,直接放。这个我们到后面再学习。
缓存落到最后都是为了提高查询效率。
一级缓存其作用域就是随着sqlSeesion生死,仅在同一个sqlSession包裹下有效,不同的sqlSession有自己不同的一级缓存。
二级缓存前提条件需要进行核心配置文件的显示开启,还需要在mapper.xml下开启cache标签。所有的数据都会先放在一级缓存中,只有会话提交(sqlSeeion.commit()),或者关闭(sqlSeeion.close())的时候,才会转存到二级缓存中!二级缓存的作用域就是一个mapper.xml下的,不同的mapper.xml有自己不同的二级缓存。随着一级缓存的关闭,相同mapper.xml查询的对象就会转存到同一个二级缓存下,不同mapper.xml查询到的对象会放在不同的二级缓存下。不同的mapper.xml要么使用自己的二级缓存池,要么引用别的mapper.xml的二级缓存池。