Mybatis学习
Mybatis学习
什么是框架

框架是软件开发中的一个解决方案,Mybatis框架解决的是持久层的问题,大大提高开发效率
框架封装了许多的细节,简化了开发者的操作
三层架构
- 表现层:展示数据
- 业务层:处理业务需求
- 持久层:和数据库

JdbcTemplate和dbutils封装的程度并不够,很繁琐

ORM:Object Relational Mapping,对象关系映射,将数据库表和实体类及其属性对应起来,也就是数据库表的表项有什么,对象的属性就有什么,可以操作实体类就实现操作数据库表
JPA是orm框架标准,主流的orm框架都实现了这个标准。
MyBatis没有实现JPA,他和orm框架的设计思路完全不一样。
MyBatis是拥抱sql,而orm则更靠近面向对象,不建议写sql,实在要写推荐你写hql代替。Mybatis是sql mapping框架而不是orm框架,当然orm和Mybatis都是持久层框架。
Hibernate是全自动ORM框架,而Mybatis是半自动的。hibernate完全可以通过对象关系模型实现对数据库的操作,拥有完整的JavaBean对象与数据库的映射结构来自动生成sql。而mybatis仅有基本的字段映射,对象数据以及对象实际关系仍然需要通过手写sql来实现和管理。


注意映射配置文件的位置要和dao接口包的结构相同
遵从了这样的标准,我们就无需在开发中写dao的实现类了
入门案例
创建一个Dao的接口
package com.jiading.dao;
import com.jiading.domain.User;
import java.util.List;
/**
* 用户的持久层接口
*/
public interface IUserDao {
List<User> findAll();
}
在resources目录下创建sqlMapConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- mybatis的主配置文件-->
<configuration>
<!-- 配置环境-->
<environments default="mysql">
<!-- 配置mysql的环境-->
<environment id="mysql">
<!-- 配置事务类型-->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源,使用连接池-->
<dataSource type="POOLED">
<!-- 配置连接数据库的基本信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--指定映射配置文件的位置-->
<mappers>
<mapper resource="com/jiading/dao/IUserDao.xml"></mapper>
</mappers>
</configuration>
datasource的type这里选择POOLED是使用连接池,它使用的是Mybatis自己实现的连接池
关于Mybatis连接池,看这篇文章:https://www.cnblogs.com/jiading/articles/13408835.html
同时创建com.jiading.dao文件夹,在其中创建IUserDao.xml文件:
映射配置文件指的是每个Dao独立的配置文件
<?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">
<!-- namespace:dao的全限定类名-->
<mapper namespace="com.jiading.dao.IUserDao">
<!-- 配置查询所有
id:dao方法的名称
resultType:指定结果集的类型-->
<select id="findAll" resultType="com.jiading.domain.User">
select * from user;
</select>
</mapper>
测试类:
package com.jiading.test;
import com.jiading.dao.IUserDao;
import com.jiading.domain.User;
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;
import java.util.List;
public class MybatisTest {
public static void main(String[] args) throws IOException {
//1. 读取配置文件
InputStream in= Resources.getResourceAsStream("sqlMapConfig.xml");
//2. 创建sqlsessionfactory
//builder为我们创建工厂对象
SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
SqlSessionFactory factory=builder.build(in);
//3.使用工厂模式生产sqlSession对象
SqlSession session=factory.openSession();
//4. 使用sqlSession创建Dao接口的代理对象
IUserDao userDao=session.getMapper(IUserDao.class);
//5. 使用代理对象执行方法
List<User> users=userDao.findAll();
for (User user:users) {
System.out.println(user);
}
//6. 释放资源
session.close();
in.close();
}
}
session的getMapper是一个动态代理方法
使用builder的构建者模型,隐藏了对象的创建过程,使得使用者直接调用方法就能拿到对象,降低了对象创建的难度
getMapper使用了代理模式,使得在不修改源码的基础上可以对对象进行增强
使用工厂模式进行生产降低了类之间的耦合度
将这些对象的创建过程都暴露出来而不是封装好、只留一个接口,是为了灵活和可扩展性
步骤

使用注解进行配置
注解配置更加高效和简便
sqlMapConfig.xml
<!--注解配置,此处用class属性指定被注解的dao全限定类名-->
<mappers>
<mapper class="com.jiading.dao.IUserDao"></mapper>
</mappers>
IUserDao.java
package com.jiading.dao;
import com.jiading.domain.User;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* 用户的持久层接口
*/
public interface IUserDao {
@Select("select * from user")
List<User> findAll();
}
自己写实现类
实现类其实也可以自己写的,虽然没什么意义,一般大家都不自己写

对于测试类:

Mybatis原理分析
查询
mybatis在使用代理dao的方式实现查询时做的事是:
- 创建代理对象
- 在代理对象中调用selectList方法

以key-value的形式存储,key是方法的全限定类名和方法名拼接起来的,value是sql和domain的path构成的mapper对象
创建代理对象

我们在InvocationHandler的接口的实现类中调用session的selectList()方法
自己实现Mybatis框架
这个项目可以帮助我们更好地理解Mybatis的原理,项目完整的代码看这里:https://gitee.com/jiadingMayun/MyBatisSourceCodeAnalysis
Mybatis实现CRUD
这里有一点要注意,就是模糊搜索的时候:
- sql应该写成where xxx like #
- username左右应该分别有一个百分号,这个百分号应该在传入sql之前就拼接好
当然也可以写成where xxx like '%${username}%'的方式,#和$的区别可以看这篇文章:https://www.cnblogs.com/jiading/articles/12570353.html, 如果这样写的话sql就是直接拼接好的,但是这样会有sql注入的风险
插入后如何返回新建用户的id?

这其实是用到了一个sql语句,叫select last_insert_id();
写出来就是这样:

keyProperty对应实体类,keyColumn对应数据库表的列名
这样的话,它就会把返回的那个id值封装到实体类的Id属性中
Mybatis的参数深入

pojo对象就是java bean,也就是我们之前一直用的实体类
什么是ognl表达式呢?

{}中的写法就是ognl表达式的写法
pojo的包装对象是为了使用现有的对象通过组合形成新的对象来应对更复杂的查询情况
例如我们创建了一个QueryVo对象,封装了User对象,之后在查询中如果要用到User对象的属性,就需要写user.属性名称了,因为我们的ParameterType是QueryVo:
<select id="findUserByVo" parameterType="com.jiading.domain.QueryVo" resultMap="userMap">
select * from user where username like #{user.username};
</select>
javaBean属性名称和数据库列名不一样怎么办?
如果Bean中属性的名称和SQL语句中不一致怎么办,查询的时候其实好办,就把#{}里的名字改了就行,主要是封装结果的时候怎么办?
最简单的办法就是起别名,这是最简单的、行之有效的方法

也可以在Dao的xml文件中配置属性对应关系
<resultMap id="userMap" type="com.jiading.domain.User">
<!--主键字段的对应,property中是Java类的属性的名称,column是myBatis的属性的名称
-->
<id property="userId" column="id"></id>
<!--非主键字段的对应-->
<result property="userName" column="username"></result>
<result property="userAddress" column="address"></result>
<result property="userSex" column="sex"></result>
<result property="userBirthday" column="birthday"></result>
</resultMap>
此外,返回值也要从resultType变成resultMap
<!-- 根据id查询用户
使用resultMap属性映射-->
<select id="findById" parameterType="INT" resultMap="userMap">
select * from user where id = #{uid}
</select>
这样的效率会提高
Mybatis实现DAO层的开发
自己编写实现类
这个之前已经说过了,不是常用的真实情况,这里作为了解就好了
这里说一下整个过程
-
测试类根据xml配置创建SqlSessionFactory,并传入DAO的实现类中
@Before public void init() throws IOException { in = Resources.getResourceAsStream("sqlMapConfig.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); userDao = new UserDaoImpl(factory); } -
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="com.jiading.dao.IUserDao"> <!--配置查询所有--> <select id="findAll" resultType="com.jiading.domain.User"> select * from user </select> <insert id="saveUser" parameterType="com.jiading.domain.User"> <!-- 返回保存的表项的id 在mysql中可以在插入完之后执行select last_insert_id();获取,这里需要写到selectKey中 keyProperty对应的是对象的属性,keyColumn对应的是表项,order表示这里的sql什么时候执行,after表示插入之后 --> <selectKey keyProperty="id" keyColumn="id" order="AFTER" resultType="java.lang.Integer"> select last_insert_id(); </selectKey> insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday}); </insert> <update id="updateUser" parameterType="com.jiading.domain.User"> update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id}; </update> <delete id="deleteUser" parameterType="java.lang.Integer"> <!--注意对于基本类型和基本类型的包装类,如果只有一个参数时,参数的类型可以随意写--> delete from user where id=#{id}; </delete> <!-- 根据id查询用户 使用resultMap属性映射--> <select id="findById" parameterType="INT" resultType="com.jiading.domain.User"> select * from user where id = #{uid} </select> <!-- 根据名称模糊查询 --> <select id="findByName" parameterType="string" resultType="com.jiading.domain.User"> select * from user where username like #{name} <!--这里的模糊查询前后的%需要写到name中 还有一种写法是: select * from user where username like '%${value}%',注意这里不是#{},而是${},其实这两种方法都是传递pojo对象的,作用一样 这种写法要求这里写的必须是value。它其实是一种字符串拼接,我们之前用的是占位符。占位符相对来说更好,可以防止注入攻击,所以这种方法我们一般不用 --> </select> <!-- 获取用户的总记录条数 --> <select id="findTotal" resultType="int"> select count(id) from user; </select> </mapper> -
DAO实现类中创建SqlSession并调用其相关CRUD方法进行操作
@Override public List<User> findAll() { SqlSession session=factory.openSession(); List<User>users=session.selectList("com.jiading.dao.IUserDao.findAll"); session.commit(); session.close(); return users; }相当于把框架自动化的一部分又手动实现了一遍,没太大意义
Mybatis工作原理
见这篇博文:https://www.cnblogs.com/jiading/articles/12599865.html
xml配置
properties
将一些配置信息放到properties中,xml其他地方如果使用的话就使用${name}引用即可,这样便于修改

当然,进一步地,我们也可以把这个properties放到外部的配置文件中

typeAlias和package
给数据类型起别名

也可以使用package标签给整个包下的类都注册别名,此时只需要使用类名即可,一般我们把domain包给注册了,比较方便

在mappers里也有一个package标签,它可以自动找到该包下所有dao的接口

Mybatis连接池
实际开发中都会使用连接池,以减少获取连接所消耗的时间
Mybatis的连接池提供了3种方式的配置
配置的位置:
-
主配置文件:SqlMapConfig.xml中的dataSource标签,type属性
- POOLED:采用传统的Javax.DataSource规范的连接池,由Mybatis实现
- UNPOOLED:采用传统的获取连接的方式,虽然也实现了Javax.DataSource接口,但是并没有使用连接池的思想
- JNDI:采用服务器提供的JNDI技术来实现获取DataSource对象,不同的服务器拿到的DataSource不一样。如果不是Web或者Maven的war工程,不能使用该技术。例如,如果使用tomcat服务器,使用的就是tomcat的dbcp连接池
Mybatis内部分别定义并实现了java.sql.DataSource接口的UnpooledDataSource和PooledDataSource来表示UNPOOLED、POOLED类型的数据源。java.sql.DataSource主要定义的就是getConnection方法,两个实现类分别用连接池和不用连接池实现了这个方法

idle意思是空闲的,idleConnections是一个ArrayList
全过程如下:
- 如果空闲连接还有,就从头部取一个出来
- 如果没有,但是当前连接数小于最大连接数,就创建一个新连接
- 如果也不行,就从activeConnections获取活动中最老的连接,如果它的使用时间超过了最大的checkoutTime,就把它从activeConnections中删除并用它的资源创建一个新的连接
- 如果没有超过时间,就只能先等待了
所以其实是有两个池,一个idleConnections,一个activeConnections
完整函数代码如下:
private PooledConnection popConnection(String username, String password) throws SQLException {
boolean countedWait = false;
PooledConnection conn = null;
long t = System.currentTimeMillis();
int localBadConnectionCount = 0;
while(conn == null) {
synchronized(this.state) {
PoolState var10000;
if (!this.state.idleConnections.isEmpty()) {
conn = (PooledConnection)this.state.idleConnections.remove(0);
if (log.isDebugEnabled()) {
log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
}
} else if (this.state.activeConnections.size() < this.poolMaximumActiveConnections) {
conn = new PooledConnection(this.dataSource.getConnection(), this);
if (log.isDebugEnabled()) {
log.debug("Created connection " + conn.getRealHashCode() + ".");
}
} else {
PooledConnection oldestActiveConnection = (PooledConnection)this.state.activeConnections.get(0);
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
if (longestCheckoutTime > (long)this.poolMaximumCheckoutTime) {
++this.state.claimedOverdueConnectionCount;
var10000 = this.state;
var10000.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
var10000 = this.state;
var10000.accumulatedCheckoutTime += longestCheckoutTime;
this.state.activeConnections.remove(oldestActiveConnection);
if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
try {
oldestActiveConnection.getRealConnection().rollback();
} catch (SQLException var16) {
log.debug("Bad connection. Could not roll back");
}
}
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
oldestActiveConnection.invalidate();
if (log.isDebugEnabled()) {
log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
}
} else {
try {
if (!countedWait) {
++this.state.hadToWaitCount;
countedWait = true;
}
if (log.isDebugEnabled()) {
log.debug("Waiting as long as " + this.poolTimeToWait + " milliseconds for connection.");
}
long wt = System.currentTimeMillis();
this.state.wait((long)this.poolTimeToWait);
var10000 = this.state;
var10000.accumulatedWaitTime += System.currentTimeMillis() - wt;
} catch (InterruptedException var17) {
break;
}
}
}
if (conn != null) {
if (conn.isValid()) {
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
conn.setConnectionTypeCode(this.assembleConnectionTypeCode(this.dataSource.getUrl(), username, password));
conn.setCheckoutTimestamp(System.currentTimeMillis());
conn.setLastUsedTimestamp(System.currentTimeMillis());
this.state.activeConnections.add(conn);
++this.state.requestCount;
var10000 = this.state;
var10000.accumulatedRequestTime += System.currentTimeMillis() - t;
} else {
if (log.isDebugEnabled()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
}
++this.state.badConnectionCount;
++localBadConnectionCount;
conn = null;
if (localBadConnectionCount > this.poolMaximumIdleConnections + this.poolMaximumLocalBadConnectionTolerance) {
if (log.isDebugEnabled()) {
log.debug("PooledDataSource: Could not get a good connection to the database.");
}
throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
}
}
}
}
}
if (conn == null) {
if (log.isDebugEnabled()) {
log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
}
throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
} else {
return conn;
}
}
Mybatis的事务
MyBatis可以在sqlSession的程度实现事务管理
例如我们在测试类中:
@Before
public void init() throws IOException {
in = Resources.getResourceAsStream("sqlMapConfig.xml");
factory = new SqlSessionFactoryBuilder().build(in);
session = factory.openSession();
userDao = session.getMapper(IUserDao.class);
}
@After
public void distroy() throws IOException {
//提交事务,这里的事务是需要手动提交的
session.commit();
session.close();
in.close();
}
我们也可以在创建sqlSession的时候传入布尔值开启自动提交:

当然,这个程度还是不够,因为它只是在dao层实现了事务,而我们往往是需要在service层实现,所以只靠Mybatis还是不行,我们往往是用spring的aop来实现事务的
动态SQL
if标签
注意在if中要使用and 或者or关键字,而不能使用&& 或者||
一个实例:

可以将其看都是在字符串拼接程度上的判断,前面1=1是为了让如果后面都是false的话加的那个where关键字不是空的,然后在if中我们需要写上and 关键字来和前面的where条件拼接
where标签
where标签可以解决前面使用where 1=1这个没意义的内容

foreach标签
foreach是为了查询的时候where xxx in的时候如何传入一个列表作为in的内容的

{}中的变量名要和item的值对应
sql标签
sql标签可以抽取重复的sql语句,下面直接引用


这里要注意:
- 如果使用了sql标签的sql片段,就建议在后面使用其他动态SQL的标签来构造SQL,不要直接在后面拼sql文本
- 因为是公共的判断,所以不要在里面加分号,否则拼到其他sql中时会报错
多表查询
其实可以使用多条SQL来完成,但是如果要使用一条SQL完成,也就是使用多表查询的方式的话,就需要在实体类中进行修改了
对于一对一的情况,就是在一个实体类中封装另外一个实体类。这在ResultMapping中用association标签来表示

例如一对多或者多对多,我们就需要在查询的实体类中设置List来存放另外一个实体类的对象,在ResultMapping中我们用Collection标签来说明

JNDI数据源
JNDI(java Naming and Directory Interface),是sun推出的一套规范,属于JAVAEE的技术之一,目的是模仿Windows中的注册表在服务器中注册数据源

使用流程
1.1 创建Maven的war工程并导入坐标
从archtype(骨架)创建,选择maven-archtype-webapp进行创建

<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
</dependency>
</dependencies>
1.2 在webapp文件下创建META-INF目录

1.3 在META-INF目录中建立一个名为context.xml的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<Context>
<!--
<Resource
name="jdbc/eesy_mybatis" 数据源的名称,可以自己指定
type="javax.sql.DataSource" 数据源类型
auth="Container" 数据源提供者,这里写的是由容器提供,例如tomcat
maxActive="20" 最大活动数
maxWait="10000" 最大等待时间
maxIdle="5" 最大空闲数
username="root" 用户名
password="1234" 密码
driverClassName="com.mysql.jdbc.Driver" 驱动类
url="jdbc:mysql://localhost:3306/eesy_mybatis" 连接url字符串
/>
-->
<Resource
name="jdbc/eesy_mybatis"
type="javax.sql.DataSource"
auth="Container"
maxActive="20"
maxWait="10000"
maxIdle="5"
username="root"
password="1234"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/eesy_mybatis"
/>
</Context>
1.4 修改SqlMapConfig.xml中的配置
java:comp/env/是一个固定路径,后面的jdbc/eesy_mybatis是自己的名称
这里要注意,JNDI是依赖于服务器的,所以必须启动服务器,例如tomcat才能获取到数据源,所以必须通过tomcat进行测试,而不能直接使用测试类
Mybatis延迟加载与缓存

对多的一般延迟加载,对一的一般立即加载
延迟加载
实现延迟加载的步骤如下:
-
修改xml文件,在查询方法中只写查询外部对象的sql,内部对象的查询方法写在association的select标签中,查询的属性写在column标签中
<mapper namespace="com.itheima.dao.IAccountDao"> <!-- 建立对应关系 --> <resultMap type="account" id="accountMap"> <id column="aid" property="id"/> <result column="uid" property="uid"/> <result column="money" property="money"/> <!-- 它是用于指定从表方的引用实体属性的 --> <association property="user" javaType="user" select="com.itheima.dao.IUserDao.findById" column="uid"> </association> </resultMap> <select id="findAll" resultMap="accountMap"> select * from account </select> </mapper>注意select的值是一个方法,我们需要准备这样一个方法
-
在全局的xml文件中开启延迟加载
![image-20200802211846688]()
<settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> </settings>此时,如果只get外部对象的信息,内部对象的信息就不会被查询到
对于一对多和多对多关系,使用collections进行配置也是一样的道理
缓存
什么是缓存

缓存终究会存在一个数据同步问题,这是怎么也没办法避免的(参考redis),所以如果对数据的正确性非常在意的话就不要在这个数据上使用缓存
Mybatis的一级缓存

由此可知,一级缓存是依赖于sqlSession的,所以如果这个session被关闭了,缓存的结果就没有了
一级缓存的例子:

连续查两次的话,其实只会执行一次,然后返回的对象也是同一个(不是指值相同,而是完全地同一个对象)

清空一下,就没有了
一级缓存的同步

所以对于同一个session是同步安全的,有修改就会清空缓存,但是多线程下却不能保证
二级缓存

二级缓存不是默认开启的,需要多个步骤进行开启(很明显,二级缓存要保存的数据量很大,同步问题也更突出,所以不是默认打开的)

测试二级缓存
-
SqlMapConfig.xml
![image-20200802213716935]()
这一步可以省略,因为默认就是true
-
IUserDao.xml
![image-20200802213816282]()
整体上以及方法上都要设置
-
执行

注意最后对象的==结果是false,因为二级缓存缓存的是数据而不是对象,每次对象都是新建的
注解开发
注解开发省略的是IUserDao.xml文件,全局的SqlMapConfig.xml不能省略
SqlMapConfig.xml中的mappers下是package标签,里面指定的是接口对象所在的包
注意注解开发和xml开发是不能并存的,也就是如果用注解的话,resources下就不能再有我们之前用的相同路径下的xml文件了,否则不管全局xml文件的mappers如何设置,一定会报错
注解实现ResultMapping

复用resultMap:

注解实现多表查询

查询一个对象时

查询多个对象时
注解开启二级缓存
一级缓存不用开启
在全局配置文件中的开启和上面一样
在Dao接口中开启:

注意注解中只需要在整个接口上开启即可,该接口的所有方法都会开启二级缓存
传参处理
具体可以看这篇文章:https://www.cnblogs.com/jiading/articles/13423428.html
总结一下,对于传入单个参数时,无论是基本类型还是复杂类型,都可以不适用@Param注解,直接写参数名即可,最好我们在#{}写的参数名和方法中的参数名一样(对于基本类型),但是其实即使不一样也不会出错;但是如果是多个参数的话,要么用Index(仅指基本类型),要么用@Param给他们确定一个名称





浙公网安备 33010602011771号