MyBatis
中文官方文档
Github项目地址
Maven下载地址
学习视频
文章目录
简介
- MyBatis 是一款优秀的持久层框架,SQL于代码分离
- 它支持自定义 SQL、存储过程以及高级映射。
- 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
- 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
- MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。
持久层
- 持久化:数据持久化
- 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
- 内存是断电即失的
- 数据库(jdbc)、io使文件持久化
- 持久层:完成持久化工作的代码块
- Dao层、Service层、Controller层…
MyBatis环境搭建
- 创建一个数据库(数据库环境搭建)
- 新建项目:新建一个普通的maven项目作为父工程
- 导入maven依赖:mysql + MyBatis +junit:
<!--mysql连接数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<!--mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
- 新建一个子项目
- 编写MyBatis核心配置文件resources下新建mybatis-config.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--configuration核心配置文件-->
<configuration>
<!--环境:可以配置多套环境-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${com.mysql.jdbc.Driver}"/>
<property name="url" value="${jdbc:mysql://localhost:3306/school?useSSL=true&useUnicode=true&characterEncoding=UTF-8}"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--每一个Mapper.xml都要注册-->
<mappers>
<mapper resource="com/huang/Dao/ClassMapper.xml"/>
</mappers>
</configuration>
- 编写myBatis工具类
//工具类
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory=null;
static {
try {
//1. 获取SqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (Exception e) {
e.printStackTrace();
}
}
//2. 获得sqlsession的实例
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
- 编写代码:
- 编写实体类
- 编写Dao接口
public interface ClassDao {
public List<test> getClassObject();
}
- 实现接口:实现类由原来的实现类转换为一个mapper配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace=命名空间-->
<mapper namespace="com.huang.Dao.ClassDao">
<!--select查询语句-->
<select id="getClassObject" resultType="com.huang.pojo.test">
select * from school.test2
</select>
</mapper>
- 测试
- 核心配置文件中注册mappers
- 可能遇到的问题:
- 配置文件没有注册
- 绑定接口错误
- 方法名不对
- 返回类型不对
- Maven导出资源问题
public class DaoTest {
@Test
public void test(){
//获取sqlSession对象
SqlSession sqlSession = MyBatisUtils.getSqlSession();
//方法一:getMapper()
ClassDao mapper = sqlSession.getMapper(ClassDao.class);
List<test> classObject = mapper.getClassObject();
for (test test : classObject) {
System.out.println(test.toString());
}
//方法二:
List<test> classObject2 = sqlSession.selectList("com.huang.Dao.ClassDao.getClassObject");
for (test test : classObject2) {
System.out.println(test.toString());
}
//关闭sqlSession
sqlSession.close();
}
}
CRUD(增删改查)
- namespace中的包名要和mapper的包名一致
- 增删改必须要提交事务
Select
- 选择查询语句
- id:就是对应的namespace中的方法名
- resultType:sql语句执行的返回值
- parameterType:参数类型
- 增加接口
//根据id查询用户
test getClassById(int id);
- 写sql配置文件
<!--根据参数查询-->
<select id="getClassById" resultType="com.huang.pojo.test" parameterType="int">
select * from school.test2 where id=#{id}
</select>
- 测试
@Test
public void testgetClassById() {
//获取sqlSession对象
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.getSqlSession();
ClassMapper mapper = sqlSession.getMapper(ClassMapper.class);
test classObject = mapper.getClassById(1);
System.out.println(classObject.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭sqlSession
sqlSession.close();
}
}
insert
- 插入数据
- 插入数据没有返回值
- 增加接口
//插入数据
int addClass(test t);
- 编写sql配置
<!--插入数据-->
<insert id="addClass" parameterType="com.huang.pojo.test">
insert into school.test2(class_name) values (#{class_name})
</insert>
- 测试
@Test
public void testInsert(){
//获取sqlSession对象
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.getSqlSession();
ClassMapper mapper = sqlSession.getMapper(ClassMapper.class);
mapper.addClass(new test(4,"大一"));
//提交事务
sqlSession.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭sqlSession
sqlSession.close();
}
}
Update
- 修改数据
- 增加接口
//修改数据
int updateClass(test t);
- 编写sql配置文件
<!--修改数据-->
<update id="updateClass" parameterType="com.huang.pojo.test">
update school.test2 set class_name=#{class_name} where id=#{id};
</update>
- 测试数据
@Test
public void testUpdate(){
//获取sqlSession对象
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.getSqlSession();
ClassMapper mapper = sqlSession.getMapper(ClassMapper.class);
mapper.updateClass(new test(1,"大二"));
//提交事务
sqlSession.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭sqlSession
sqlSession.close();
}
}
delete
- 删除数据
- 编写接口
//删除数据
int deleteClass(int id);
- 编写sql配置文件
<!--删除数据-->
<delete id="deleteClass" parameterType="int">
delete from school.test2 where id=#{id}
</delete>
- 测试
@Test
public void testDelete(){
//获取sqlSession对象
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.getSqlSession();
ClassMapper mapper = sqlSession.getMapper(ClassMapper.class);
mapper.deleteClass(4);
//提交事务
sqlSession.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭sqlSession
sqlSession.close();
}
}
Map替代实体类
-
我们的实体类,或者数据库中的表,字段或参数过多,我们应该考虑用Map
-
map传参数,直接在sql中取出来即可
-
只有一个基本类型参数的情况下,可以直接在sql中取到
-
多个参数用Map,或注解
-
传递的参数靠Map来代替
//插入数据Map
int addClass2(Map map);
- sql注册修改为map,value名称可随意写可以随便写
<!--插入数据-->
<insert id="addClass2" parameterType="Map">
insert into school.test2(class_name) values (#{map_class_name})
</insert>
- 测试数据
Map<String, Object> map = new HashMap<String, Object>();
map.put("map_class_name","哈哈哈哈");
mapper.addClass2(map);
模糊查询
- 在Java代码层,传递通配符%
- 在sql拼接中使用通配符
select * from school.test2 where name like "%"#{id}"%"
配置解析
核心配置文件(mybatis-config.xml)
- MyBatis的配置文件包含了会影响MyBatis行为的设置和属性信息
configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- environment(环境变量)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
环境配置(environments)
- MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中
- 不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境
- Mybatis默认的事务管理器就是JDBC,连接池:POOLED
属性(properties)
- 可以通过properties在外部进行配置,并可以进行动态替换
- 编写一个配置文件:db.properties
- 在核心配置文件中引入
<!--db.properties配置文件引入-->
<properties resource="db.properties">
<!--可以在这增加属性-->
<property name="username" value="root"/>
</properties>
- 可以直接引入外部配置文件
- 可以在其中增加一些属性
- 如果两个都有同一个字段,优先使用外部配置文件
设置(settings)
- 这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。
- 查文档:设置
类型别名(typeAliases)
- 自定义别名
- 为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,
- 意在降低冗余的全限定类名书写。
<!--给类型起别名-->
<typeAliases>
<typeAlias type="com.huang.pojo.test" alias="Test"></typeAlias>
</typeAliases>
- 指定包
- 也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean
- 扫描实体类的包,它的默认别名是这个类的类名,首字母小写
<!--给类型起别名-->
<typeAliases>
<typeAlias type="com.huang.pojo.test" alias="Test"></typeAlias>
<package name="com.huang.pojo"/>
</typeAliases>
- 实体类比较少的时候,使用第一种方式
- 实体类十分多,建议使用第二种方式
- 第一种可以自定义别名,第二种要通过在实体类上加注解注解
@Alias("test")
public class test {}
映射器(mappers)
- 注册绑定我们的Mapper文件
- 方法一:(resource)使用相对于类路径的资源引用
<!--每一个Mapper.xml都要注册-->
<mappers>
<mapper resource="com/huang/Dao/ClassMapper.xml"/>
</mappers>
- 方法二:使用映射器接口实现类的完全限定类名
- 接口和Mapper配置文件必须同名
- 接口和配置文件必须在同一个包下
<mappers>
<mapper class="com.huang.pojo.test"/>
</mappers>
- 方法三:通过package扫描包进行注入绑定
- 接口和Mapper配置文件必须同名
- 接口和配置文件必须在同一个包下
<mappers>
<package name="com.huang.Dao"/>
</mappers>
生命周期和作用域
== 理解不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题==
- SqlSessionFactoryBuilder:
- 一旦创建了 SqlSessionFactory,就不再需要它了
- 局部变量
- SqlSessionFactory:
- 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例
- 可以想象为数据库数据库连接池
- SqlSessionFactory 的最佳作用域是应用作用域
- 最简单的就是使用单例模式或者静态单例模式。
- SqlSession:
- 可以理解为连接到连接池的请求
- SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
- 用完之后赶紧关闭,否则资源会浪费
ResultMap的使用
- 解决属性名和字段名不一致的问题
- 数据库中的字段和实体类中的字段命名不一致
解决方法:
- 起别名:
select id class_name as name from school.test2 where id=#{id}
- resultMap:结果集映射
<!--//结果映射-->
<resultMap id="MyMap" type="Test">
<!--column数据库中的字段,property实体类中的属性-->
<result column="id" property="id"/>
<result column="class_name" property="name"/>
</resultMap>
<select id="getClassObject" resultMap="MyMap">
select * from school.test2
</select>
- ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
- ResultMap 的优秀之处——你完全可以不用显式地配置它们:字段名和实体类相同的命名可以不写,只写不同的
日志
日志工厂
-
如果一个数据库操作出现了异常,需要排错,日志就是最好的助手
-
myBatis提供的日志
-
SLF4J
-
LOG4J
-
LOG4J2
-
JDK_LOGGING
-
COMMONS_LOGGING
-
STDOUT_LOGGING :标准日志工厂
-
NO_LOGGING
STDOUT_LOGGING(标准日志工厂)
- 在myBatis中具体使用哪一个日志实现,在设置中实现
- 在myBatis核心配置文件中,配置我们的日志
<!--setting设置-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
LOG4J
Log4j是可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;
我们也可以控制每一条日志的输出格式;
通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
- 导入LOG4J的包:在maven的pom文件中添加
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
- 在resources目录下,新建log4j.properties配置文件
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file
#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=【%c】-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/huang.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
- 配置log4j的实现
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
- log4j的使用
- 在使用的时候导入包:import org.apache.log4j.Logger
static Logger logger = Logger.getLogger(DaoTest.class);
@Test
public void testLog4j(){
logger.info("info:进入了Log4j的方法");
logger.debug("debug:进入了debug格式");
logger.error("error:进入了error方法");
}
分页
使用Limit分页
select * from 表名 limit 个数;[0,个数]
使用myBatis实现分页(sql层面)
- 编写接口
//分页查询
List<test> getClassByLimit(Map<String,String> map);
- mapper.xml
<!--分页查询getClassByLimit-->
<select id="getClassByLimit" parameterType="map" resultType="Test" resultMap="MyMap">
select * from school.test2 limit #{startIndex},#{pageSize}
</select>
- 测试
@Test
public void testLimit(){
//获取sqlSession对象
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.getSqlSession();
ClassMapper mapper = sqlSession.getMapper(ClassMapper.class);
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("startIndex",0);
map.put("pageSize",2);
List<test> list = mapper.getClassByLimit(map);
for (test test : list) {
System.out.println(test.toString());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭sqlSession
sqlSession.close();
}
}
RowBounds类实现分页(Java代码层实现)
- 接口
List<test> getClassByRowBounds();
- mapper.xml
<select id="getClassByRowBounds" resultMap="MyMap">
select * from school.test2
</select>
- 测试
@Test
public void testRowBounds(){
//获取sqlSession对象
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.getSqlSession();
//方法二:通过java代码层面实现分页
RowBounds rowBounds = new RowBounds(0, 2);
List<test> classObject =sqlSession.selectList("com.huang.Dao.ClassMapper.getClassObject",null,rowBounds);
for (test test : classObject) {
System.out.println(test.toString());
}
}catch (Exception e){
e.printStackTrace();
}finally {
//关闭sqlSession
sqlSession.close();
}
}
插件实现分页(pagehelper)
- 具体使用方法参考使用文档
pagehelper
使用注解开发
- 像 BlogMapper 这样的映射器类来说,还有另一种方法来完成语句映射。 它们映射的语句可以不用 XML 来配置,而可以使用 Java 注解来配置。
- 注解开发的本质是利用反射机制实现
- 底层是动态代理
- 在接口上添加注解
@Select("select * from test2 where id=#{id}")
test getClassById(int id);
- 在核心配置文件上绑定接口
<mappers>
<mapper class="com.huang.Dao.ClassMapper"/>
</mappers>
- 测试使用
工具类创建时实现自动事务提交
- openSession里添加一个true
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}
注解CRUD
-
有多个参数传递的时候加上@Param
- 基本类型的参数或者String类型,需要加上
- 引用类型不需要加
- 如果只有一个基本类型可以忽略
- sql中引用的就是我们在这里@Param(“id”)中设置的属性名
-
关于#{}和${}
- #{}表示使用预编译sql,可以防止大部分SQL注入
- ${}使用的拼接字符串的形式生产sql语句,不能防止SQL注入
- 尽量使用#{}
@Select("select * from test2 where id=#{id}")
test getClassById(@Param("id") int id,@Param("name") String name);
myBatis详细实现过程
Lombok插件
- 简化实体类的操作
- 在IDEA插件市场中安装Lombok插件
- Maven中导入Lombok包
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
- 在实体类上加注解
@Getter and @Setter
@FieldNameConstants
@ToString
@EqualsAndHashCode
@AllArgsConstructor,(有参构造)@RequiredArgsConstructor and @NoArgsConstructor(无参构造)
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data :无参构造,get,set,toString,hashCode,equals
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
@val
@var
experimental @var
@UtilityClass
复杂查询
多对一查询:association
- 多个学生对应一个老师 :查询学生信息包含老师信息
- 学生实体类中有一个老师对象
- 当实体类中不只是有基本类型的数据
- 复杂属性我们需要单独处理,对象:association,集合:collection
按照查询嵌套处理(子查询)
<--
思路:
1. 查询所有的学生信息
2. 根据学生信息的tid查询老师信息------子查询
-->
<-- 查询学生信息 -->
<selsct id="getStudent" resultMap="myStudent">
select * from student
</select>
<-- 根据id查询老师信息 -->
<selsct id="getTeacher" resultType="Teacher">
select * from teacher where id=#{id}
</select>
<-- 用resultMap把学生和老师信息做结果集映射 -->
<resultMap id="myStudent" type="student">
<-- 相同字段可以不写 -->
<resule property="name" column="name"/>
<-- 复杂属性我们需要单独处理,对象:association,集合:collection -->
<association property="teacher" column="id" javaType="Teacher" select="getTeacher"/>
</resultMap>
按照结果嵌套处理(联表查询)
<--
思路:
1. 查询所有的学生信息和老师的信息
-->
<-- 查出所有信息 -->
<selsct id="getStudent" resultMap="myStudent">
select s.id sid,s.name sname,t.name tname
from student s,teacher t
where s.tid = t.id
</select>
<-- 结果集映射 -->
<resultMap id="myStudent" type="Student">
<result property="id" column="sid">
<result property="name" column="sname">
<assoction property="teacher" javaType="Teacher">
<result property="name" column="tname">
</assoction>
</resultMap>
一对多查询:collection
- 一个老师对应多个学习:查询老师信息,对应多个学生
- 老师实体类中有一个学生对象数组
- 老师实体类包含属性:id(int),name(String),students(List)
联表查询
- 接口:
Teacher getTeacher(@Param("id") int id);
- 配置文件
<-- 查询老师信息 -->
<selsct id="getTeacher" resultMap="myTeacher">
select s.id sid, s.name sname,t.name tname t.id tid
from student s,teacher t
where s.id = t.id and t.id=#{id}
</select>
<-- 结果集映射 -->
<resultMap id="myTeacher" type="Teacher">
<result property="id" column="tid">
<result property="name" column="tname">
<collection property="students" ofType="Student">
<result property="id" column="sid">
<result property="name" column="sname">
<result property="tid" column="tid">
</collection>
</resultMap>
子查询
- 接口
Teacher getTeacher(@Param("id") int id);
- 配置文件
<-- 根据老师信息 -->
<selsct id="getTeacher" resultMap="myTeacher">
select * from teacher where id=#{id}
</select>
<-- 查询学生信息 -->
<selsct id="getStudent" resultType="Student">
select * from student where tid=#{id}
</select>
<-- 用resultMap把学生和老师信息做结果集映射 -->
<resultMap id="myTeacher" type="student">
<-- 复杂属性我们需要单独处理,对象:association,集合:collection -->
<collection property="students" javaType="ArrayList" ofType="Student" select="getStudent" column="id"/>
</resultMap>
javaType和ofType
- javaType用来指定实体类中属性的类型
- ofType用来指定映射到List或者集合中的实体类类型,泛型中的约束类型
动态SQL
根据不同条件生产不同SQL语句
if
- 在后面拼接 AND title like #{title}
- 如果title不为空就会拼接后面的语句
<select id="findActiveBlogWithTitleLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>
choose (when, otherwise)
- 不想使用所有的条件,而只是想从多个条件中选择一个使用
- 类似于 Java 中的 switch 语句
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
trim (where, set)
where
- where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句
- 若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
set - set 元素会动态地在行首插入 SET 关键字
- 并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
trim
- 自定义标签:prefix(前缀),prefixOverrides
- Where标签的trim写法
<trim prefix="WHERE" prefixOverrides="AND |OR ">
</trim>
- Set标签的trim写法
<trim prefix="SET" suffixOverrides=",">
</trim>
动态SQL本质还是SQL语句,只是可以在SQL层面 ,去执行一个逻辑代码
SQL片段(sql,include)
- 抽取公共标签,复用SQL语句
<-- 抽取的sql语句 -->
<sql id="my-test-id">
<if test="title!=null"> and title=#{title} </if>
</sql>
<-- 引用抽取的片段 -->
<include refid="my-test-id"></include>
- 注意:
- 最好基于单表来定义SQL片段
- 不要存在Where标签
foreach
- 对集合进行遍历(尤其是在构建 IN 条件语句的时候)
- 元素:
- item:每一项的名字,例如id
- index:键
- collection:集合名
- open:以什么开始
- separator:分隔符
- close:以什么结尾
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
例子:
select * from test where 1=1 and (id =1 or id=2 or id=3)
- sql语句
<select id="" parameterType="map" resultType="blog">
select * from test
<where>
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id=#{id}
</foreach>
</where>
</select>
- 测试查询语句
Map map=new HashMap();
List<Integer> ids =new ArrayList<Integer>();
ids.add(1);
ids.add(2);
ids.add(3);
map.put("ids",ids);
List<blog> blogs =mapper.testForeach(map);
动态SQL就是在拼接SQL语句,我们只要保证SQL的正确性,按照SQL的格式,去排列组合
缓存
- 存在内存中的临时数据
- 将用户经常查询的数据放到缓存(内存)中,查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题
- 使用缓存可以减少和数据库的交互次数,减少系统开销,提高系统效率
- 缓存中应该存经常查询并且不经常改变的数据
MyBatis缓存
- MyBatis包含一个非常强大的查询缓存特性,它可以非常方便的定制和配置缓存,可以极大的提升查询效率
- myBatis中默认定义两级缓存:一级缓存,二级缓存
- 默认情况下,只有一级缓存开启(sqlSession级别的缓存,本地缓存)
- 二级缓存需要手动开启和配置吗,是基于namespace级别的缓存
- 为了提高扩展性,myBatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存
一级缓存
- 不用手动配置,默认开启,只在一次session中有效,也就是拿到连接到关闭这个区间段
- 缓存失效情况:
- 查询不同的东西
- 增删改操作,可能会改变原来的数据,所有要刷新缓存
- 查询不同的Mapper.xml
- 手动清理缓存:调用 sqlSession.clearCache();方法
二级缓存
- 二级缓存也叫全局缓存
- 基于namespace级别的缓存,一个命名空间对应一个二级缓存
- 工作机制:
- 一个会话查询一条数据,这个会话就会被防在当前会话的一级缓存中
- 如果会话关闭,这个会话对应的一级缓存就没了,但是我们想要的是会话关闭了,一级缓存中的数据被保存到二级缓存中
- 新的会话查询信息,就可以从二级缓存中获取内容
- 不同的mapper查出的数据会放在对应的缓存(map)中
- 小结:
- 只要开启了二级缓存,在同一个Mapper下就有效
- 所有的数据都会先放在一级缓存中
- 只有当前会话提交,或者关闭的时候,才会提交到二级缓存
使用步骤
- 开启全局缓存(二级缓存),在setting在显示开启
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!--显示开启缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
- 在要使用二级缓存的mapper.xml中添加cache标签,也可以自定义标签
<cache/>
<!--或者-->
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
- 要将实体类序列化:基础 implements Serializable 接口
- 否则会报错:java.io.NotSerializableExcrption
缓存原理
自定义缓存(Ehcache)
-
现在主要用Redis做缓存
-
一种广泛使用的开源Java分布式缓存。主要面向通用缓存
- 导入jar包:
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
- 设置mapper.xml中的type:
<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 path="./tmpdir/Tmp_EhCache"/>
<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>