MyBatis => XML 配置【脚手架文档·原创·个人私用】
关于 XML
<![CDATA[]]> 代码块
<![CDATA[]]>
块,这是用来包裹文本内容的一种方式,表示其中的文本应当被视为字符数据而不是XML标签。
<test>
<name><![CDATA[我看你<><><>是一点都不懂哦>>>]]></name>
</test>
拿到 XML 文件内容
// 创建DocumentBuilderFactory对象
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 创建DocumentBuilder对象
try {
DocumentBuilder builder = factory.newDocumentBuilder();
Document d = builder.parse("file:mappers/test.xml");
// 每一个标签都作为一个节点
NodeList nodeList = d.getElementsByTagName("test"); // 可能有很多个名字为test的标签
Node rootNode = nodeList.item(0); // 获取首个
NodeList childNodes = rootNode.getChildNodes(); // 一个节点下可能会有很多个节点,比如根节点下就囊括了所有的节点
//节点可以是一个带有内容的标签(它内部就还有子节点),也可以是一段文本内容
for (int i = 0; i < childNodes.getLength(); i++) {
Node child = childNodes.item(i);
if(child.getNodeType() == Node.ELEMENT_NODE) //过滤换行符之类的内容,因为它们都被认为是一个文本节点
System.out.println(child.getNodeName() + ":" +child.getFirstChild().getNodeValue());
// 输出节点名称,也就是标签名称,以及标签内部的文本(内部的内容都是子节点,所以要获取内部的节点)
}
} catch (Exception e) {
e.printStackTrace();
}
学习和使用XML只是为了更好地去认识Mybatis的工作原理,以及如何使用XML来作为Mybatis的配置文件,这是在开始之前必须要掌握的内容(使用Java读取XML内容不要求掌握,但是需要知道Mybatis就是通过这种方式来读取配置文件的)
不仅仅是Mybatis,包括后面的Spring等众多框架都会用到XML来作为框架的配置文件!
⭐MyBatis 配置文件
注意事项:实体类 建议只写 getter、setter、toString 方法!别动不动就 把有参构造写上去!因为有参构造是需要指定构造函数的!否则会报错。。会出现很多奇怪的错误 ~
⭐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>
<!--其它配置信息-->
<settings>
<!--数据表中字段 带有下划线让其可以在 MyBatis上 用驼峰就行,即 my_test => myTest -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!--二级缓存是否开启-->
<setting name="cacheEnabled" value="false"/>
</settings>
<!-- 需要在environments的上方 -->
<!-- 起别名 => 这样的话就可以在 xml 里面写 Student 相当于 com.test.entity.Student-->
<typeAliases>
<typeAlias type="com.test.entity.Student" alias="Student"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${驱动类(含包名)}"/>
<property name="url" value="${数据库连接URL}"/>
<property name="username" value="${用户名}"/>
<property name="password" value="${密码}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper url="file:mappers/TestMapper.xml"/>
<!--这里用的是url 且 file: 代表的是该项目根目录下-->
</mappers>
</configuration>
${数据库连接URL}
:jdbc:mysql://localhost:3306/demo?useUnicodetrue&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=true
${驱动类(含包名)}
:com.mysql.cj.jdbc.Driver
PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"
:是 定义了一些标签的 文档(因为只有这样Mybatis才能正确识别我们配置的内容)
⭐映射器 xxxMapper.xml
不绑定 接口写法
创建一个mapper文件夹,新建名为TestMapper.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="TestMapper"> <!--namespace 要与对应 接口名、xml文件名保持一致!这只是一种好习惯-->
<!--id = 接口定义的方法名 resultType = 方法返回的类型(这里建议把包名写全)-->
<select id="selectStudent" resultType="com.test.entity.Student">
select * from student <!--要执行的 SQL 语句-->
</select>
</mapper>
public static void main(String[] args) throws FileNotFoundException {
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(new FileInputStream("mybatis-config.xml")); // 读取 mybatis-config.xml 配置文件去 Builder sqlSessionFactory
// 拿到 sqlSessionFactory 之后,可以用它去 进行数据库的连接,返回一个 SqlSession 即数据库会话对象
try (SqlSession sqlSession = sqlSessionFactory.openSession(true)){
// 下方是示例
// 如果没有使用接口的话,那么就得通过 sqlSession.方法("xml 文件中id/方法名 来寻找")
List<Student> student = sqlSession.selectList("selectStudent");
student.forEach(System.out::println);
}
}
我们可以依赖于不同的 mybatis-config.xml 创建多个 SqlSessionFactory 来进行 不同数据库的连接 或 不同账户对同一个数据库的连接,因为会话彼此之间是独立的!
⭐绑定 接口写法
<?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.test.mapper.TestMapper">
<select id="selectStudent" resultType="com.test.entity.Student">
select * from student
</select>
</mapper>
如果一旦 需要绑定接口!则就需要把 xml 文件多拽到 接口同目录下!此时xml文件将作为内部资源!
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${驱动类(含包名)}"/>
<property name="url" value="${数据库连接URL}"/>
<property name="username" value="${用户名}"/>
<property name="password" value="${密码}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/test/mapper/TestMapper.xml"/>
</mappers>
</configuration>
public static void main(String[] args) {
try (SqlSession sqlSession = MybatisUtil.getSession(true)){
// 通过 sqlSession.getMapper(Class对象) 拿到对应的 接口实现类,MyBatis 会在运行时帮我们实现这个实现类。
TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
List<Student> student = testMapper.selectStudent();
student.forEach(System.out::println);
}
}
更改环境实现连接其它数据库
我们可以在不写新的 xml 文件的基础上,切换到不同环境 去连接 数据库。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/demo?useUnicodetrue&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=true"/>
<property name="username" value="root"/>
<property name="password" value="123123"/>
</dataSource>
</environment>
<environment id="mysqlEnvironment">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mysql?useUnicodetrue&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=true"/>
<property name="username" value="root"/>
<property name="password" value="123123"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper url="file:mappers/TestMapper.xml"/>
<!--这里用的是url 且 file: 代表的是该项目根目录下-->
</mappers>
</configuration>
sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(new FileInputStream("mybatis-config.xml"), "mysqlEnvironment"); // 提供环境的 ID 就行!
扫描包改别名(建议用!)和注解改别名
<?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的上方 -->
<!-- 起别名 => 这样的话就可以在 xml 里面写 Student 相当于 com.test.entity.Student-->
<typeAliases>
<package name="com.test.entity"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${驱动类(含包名)}"/>
<property name="url" value="${数据库连接URL}"/>
<property name="username" value="${用户名}"/>
<property name="password" value="${密码}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper url="file:mappers/TestMapper.xml"/>
<!--这里用的是url 且 file: 代表的是该项目根目录下-->
</mappers>
</configuration>
直接让Mybatis去扫描一个包,并将包下的所有类自动起别名(别名为首字母小写的类名)
@Data
@Alias("lbwnb")
public class Student {
private int sid;
private String name;
private String sex;
}
增删改查
增删改
int addStudent(Student student);
int deleteStudent(Integer id);
int updateStudent(Student student);
<?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.test.mapper.TestMapper">
<!--会自动返回 修改的数据行数,是整数型-->
<insert id="addStudent" parameterType="Student">
<!--name、sex 是实体类的属性名,而非 字段名,一定要注意,在后端代码层面,除了 resultMap 里面用过 字段名进行过映射的修改,其余地方其实 都是属性名-->
insert into Student values(null, #{name}, #{sex})
</insert>
<!--如果是基本类型 parameterType = int 就行了-->
<delete id = "deleteStudent" parameterType="java.lang.Integer">
delete from Student where id = #{id}
</delete>
<update id = "updateStudent" parameterType="Student">
update Student set name = #{name}, sex = #{sex} where id = #{id}
</update>
</mapper>
当然 我们一定要注意,除了 查询 之外,剩下的 增删改 都是需要 提交事务的,否则 数据 不会 有更新变动。
sqlSession.commit();// 提交事务
关于MyBatis => xml 配置文件 【查】的正确理解
我们编写 xml 配置文件的 SQL 语句,只需要把我们 对应字段的 数据查出来就行了!
MyBatis 就会自动的将这些查出来的数据,与 实体类中同名的属性(当然你也可以用 resultMap 去自定义映射的名字),进行一一的赋值操作。
然后返回一个实体对象给我们,哪怕是 多表查询也是如此,它会把返回的所有的实体对象封装在 集合里面。
总结:我们的关注点,应该是 SQL 语句是否能查出来我们 实体类属性 对应的 那些数据!属性名和字段名是否能够对上
查(返回实体类)
<?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.test.mapper.TestMapper">
<select id="studentList" resultType="Student">
select * from student
</select>
</mapper>
查(返回 Map 类型的集合)
<select id="selectStudent" resultType="Map">
select * from student
</select>
public interface TestMapper {
// List 就是集合中的 列表
// 每个元素的类型 都是 Map 即键值对
List<Map> selectStudent();
}
⭐自定义 字段与属性的映射
<?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.test.mapper.TestMapper">
<resultMap id="Test" type="Student">
<result column="sid" property="sid"/>
<result column="sex" property="name"/>
<result column="name" property="sex"/>
</resultMap>
<select id="studentList" resultMap="Test">
select * from student
</select>
</mapper>
条件查询
Student getStudentBySid(int sid); // 其实就是有参数的接口方法
<?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.test.mapper.TestMapper">
<select id="getStudentBySid" parameterType="int" resultType="Student">
select * from student where sid = #{sid}
</select>
</mapper>
{参数名}:安全的参数,通过 预编译 prepareStatement
${参数名}:不安全的参数,通过 Statement
⭐联表查询
⭐一对多(把多个对象存储在集合)
@Data
public class Teacher {
int tid;
String name;
List<Student> studentList; // sid 对应的应该是 Student 表里的学生 才对
// 在实体类层面,我们理应 把这里的 sid 设计为 Student 引用类型的 集合,常用 List<>
// 然后 xml 就要符合 Student 的各个属性,即我们必须得查询出来 Student 需要的数据
// id、name
}
<select id="getTeacherByTid" resultMap="asTeacher">
select *, teacher.name as tname from student inner join teach on student.sid = teach.sid
inner join teacher on teach.tid = teacher.tid where teach.tid = #{tid}
</select>
<resultMap id="asTeacher" type="Teacher">
<id column="tid" property="tid"/>
<result column="tname" property="name"/> <!--这里是说 查出来的结果集 tname 字段的数据会赋值给 名字为 name 的属性-->
<!--第三个属性是 集合,所以我们在这里要声明 collection -->
<!-- property 还是属性名,ofType 是每个元素的类型-->
<collection property="studentList" ofType="Student">
<!--一般集合存储的也都是 引用类型,那就肯定有属性-->
<id property="sid" column="sid"/> <!--主键对应的属性 和 字段-->
<result column="name" property="name"/><!--非主键对应的属性 和 字段-->
<result column="sex" property="sex"/>
</collection>
</resultMap>
⭐多对一(把多个对象存储在集合)
其实就是只用到了 association
而不是集合,因为多个对一个,肯定不是集合嘛。然后主要核心工作就交给 SQL 语句去拿到数据就完事了。
@Data
@Accessors(chain = true)
public class Student {
private int sid;
private String name;
private String sex;
private Teacher teacher;
}
@Data
public class Teacher {
int tid;
String name;
}
<resultMap id="test2" type="Student">
<id column="sid" property="sid"/>
<result column="name" property="name"/>
<result column="sex" property="sex"/>
<association property="teacher" javaType="Teacher">
<id column="tid" property="tid"/>
<result column="tname" property="name"/>
</association>
</resultMap>
<select id="selectStudent" resultMap="test2">
select *, teacher.name as tname from student left join teach on student.sid = teach.sid
left join teacher on teach.tid = teacher.tid
</select>
个人不推荐 MyBatis 的子查询,因为实际上可以在 SQL 层面很好的实现,然后用 resultMap 进行映射就完事了。反倒是更加的轻松和简单。
⭐多构造方法实体类 报错解决
这时就需要使用constructor
标签来指定构造方法:
MyBatis 查询在返回实体对象的时候,都要调用一下构造方法。要不然对象也没法创建呀。。
<?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.test.mapper.TestMapper">
<resultMap id="test" type="Student">
<constructor>
<arg column="sid" javaType="Integer"/>
<arg column="name" javaType="String"/>
</constructor>
</resultMap>
<!--这样就会指定 只有两个参数的 构造方法,constructor
只要跟参数顺序的类型一一对应上就可以!-->
<select id="studentList" resultMap="Test">
select * from student
</select>
</mapper>
MybatisUtil 工具类
public class MybatisUtil {
//在类加载时就进行创建
private static SqlSessionFactory sqlSessionFactory;
static {
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(new FileInputStream("mybatis-config.xml"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
/**
* 获取一个新的会话
* @param autoCommit 是否开启自动提交(跟JDBC是一样的,如果不自动提交,则会变成事务操作)
* @return SqlSession对象
*/
public static SqlSession getSession(boolean autoCommit){
return sqlSessionFactory.openSession(autoCommit);
}
}
public static void main(String[] args) {
try (SqlSession sqlSession = MybatisUtil.getSession(true)){
List<Student> student = sqlSession.selectList("selectStudent");
student.forEach(System.out::println);
}
}
⭐动态SQL(MyBatis特性)
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用 动态 SQL,可以轻松彻底摆脱这种痛苦。
动态 SQL 就是 指 根据 不同的条件 生成 不同的 SQL 语句。也就是说 它的 自适应能力 会很强,会减少 我们 手写的 一些SQL。
比如说 在 项目开发的时候,我们可能会遇到 不同条件 进行 SQL 拼接的问题。
这个时候 其实 动态 SQL 就 显得 非常 牛B 了。但是 写起来 其实 也不太开心,只能说 重复的 再遇到 这种 问题的时候,复制粘贴 就好了。不需要考虑太多的事情。稍微改改就 ok。
CREATE TABLE `blog`(
`id` VARCHAR(50) NOT NULL COMMENT '博客id',
`title` VARCHAR(100) NOT NULL COMMENT '博客标题',
`author` VARCHAR(30) NOT NULL COMMENT '博客作者',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`views` INT(30) NOT NULL COMMENT '浏览量'
)ENGINE=INNODB DEFAULT CHARSET=utf8;
动态 SQL 之 IF 语句
- 解决 查询 关键字 不同 情况的 SQL 拼接。
// 例如这种提供 动态参数然后 进行查询的时候,查询的关键字有很多情况
List<Blog> queryBlogIF(Map<String, Object> map);
<?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="top.muquanyu.Mapper.BlogMapper">
<select id="queryBlogIF" parameterType="map" resultType="top.muquanyu.pojo.Blog">
<!-- where 1 = 1 是为了保证 无条件查询全部数据-->
select * from test.blog where 1 = 1
<!--test = "条件表达式"-->
<if test="title != null">
<!--成功的话就给它拼接上-->
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</select>
</mapper>
<where> 标签 的牛逼之处
我们在 写 <if> 标签的时候的,肯定会碰到如下的 事故。
<select id="queryBlogIF" parameterType="map" resultType="top.muquanyu.pojo.Blog">
select * from test.blog where
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</select>
也就是 说 可能会 出现 where 后面 进行 SQL 拼接时 的 一些 小问题。拼接 错喽 ~
那怎么 去解决呢? 这里 有一个 标签 叫 <where>
<select id="queryBlogIF" parameterType="map" resultType="top.muquanyu.pojo.Blog">
select * from test.blog
<where>
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>
意思就是说,如果我们 后面真的有条件进行拼接的话, <where> 标签就会自动变为 where 字符串,但是如果 后面没有条件字符串进行拼接的话,那么 where 标签就不会生成!
<choose> 选择标签
当你 只想 满足一个条件后,就不再去 判断其它条件了,类似于选择的作用。我们 就可以 写 choose 标签。它类似于 Java 里面的 switch。而 when 等价于 case、otherwise 等价于 default。
<select id = "findActiveBlogLike" resultType = "Blog">
select * from blog
<where>
state = 'ACTIVE'
<choose> <!--等价于 switch -->
<!--等价于 case -->
<when test = "title != null">
and title like #{title}
</when>
<!--等价于 case -->
<when>
and author_name = like #{author.name}
</when>
<!--等价于 default -->
<otherwise>
and feetured = 1
</otherwise>
</choose>
</where>
</select>
<set> 标签
在 更新 数据的时候,我们 update set 后面 要 拼接 多个 字段,然后 会 带有 逗号。这个 时候 往往 可能在 mybatis 里面 就出错了。那么 <set> 标签 就是 为了 避免 这样的问题的。
<update id="updateBlog" parameterType="map">
update test.blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author}
</if>
</set>
where id = #{id}
</update>
<trim> 标签 可以 自定义一个 规则
trim 格式: <trim prefix="" suffix="" suffixOverrides="" prefixOverrides=""></trim>
-
prefix:在trim标签内sql语句加上前缀
比如说 你要拼接 where xxxx 那么这个 prefix = "where"
-
suffix:在trim标签内sql语句加上后缀
比如说 你拼接完事后 要有个 分号,那么 你就写 suffix = ";"
-
suffixOverrides:指定去除多余的后缀内容
如:suffixOverrides=",",去除trim标签内sql语句多余的后缀","。
-
**prefixOverrides:指定去除多余的前缀内容 **
如:我们 where 后面 接的 and || or 有时候 就会 多出来,那么 就得 prefixOverrides = "and || or"
这里的 多余 前缀 是从 prefix 之后 开始计算的
<select id="queryBlogIF" parameterType="map" resultType="top.muquanyu.pojo.Blog">
select * from test.blog
<trim prefix="where" prefixOverrides="and || or">
<choose>
<when test="title != null">
and title = #{title}
</when>
<when test = "author != null">
and author = #{author}
</when>
<otherwise>
and views = #{views}
</otherwise>
</choose>
</trim>
</select>
<sql> 片段
- <sql> 标签 是 把 mybatis 的 xml 文件中,sql 拼接的 代码段,给 进行 一个 封装。然后 来回 的 去 应用。
- <foreach> 可以对 一个 常见的 业务需求 进行一个 很好的方案解决。比如说 多选 对象时,进行 查询。
<sql id="if-title-author">
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author}
</if>
</sql>
<select id="queryBlogIF" parameterType="map" resultType="top.muquanyu.pojo.Blog">
select * from test.blog
<trim prefix="where" prefixOverrides="and || or">
<include refid="if-title-author"></include>
</trim>
</select>
sql 片段 一般 都不写 <set> 、 <trim> 和 <where> ,都是 直接 写 if 片段的。是的,你没听错,如果用 sql 片段的话,里面不是 写 if 片段,那。。。有点儿不对劲
<foreach> 标签
当你 多选 对象 进行 遍历 查询的时候,我们可以 直接 用 该 标签 进行 配置。而不需要 在 java 里面 写 拼接的 SQL。
- open:整体 拼接 SQL 的前缀
- close: 整体 拼接 SQL 的后缀
- separator:每个 元素 的 分隔符
- collection:要遍历 的 集合 的 Java 代码中的 名称,直接 写就行。
<select id="getListBymultiID" resultType="top.muquanyu.pojo.Blog" parameterType="map">
select * from test.blog
<where>
<foreach collection="ids" index="index" item="id" open="(" close=")" separator=",">
id = #{id}
</foreach>
</where>
</select>
@Test
public void getListBymultiIDTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
ArrayList<String> arr = new ArrayList<>();
arr.add("dc804c0110814455bdc1a6b3f7a9f8fa");
arr.add("095edf086896421cae70fee8eb4ad56f");
arr.add("23fae76722864d7d8b8582625378fbac");
HashMap<String, Object> map = new HashMap<>();
map.put("ids",arr);
List<Blog> listBymultiID = mapper.getListBymultiID(map);
for (Blog blog : listBymultiID) {
System.out.println(blog);
}
sqlSession.close();
}
- 当我们 单参数数组的时候,我们这个 <foreach> 写法就不一样了。它必须是
collection 默认值为 array
,并且parameterType = java.util.ArrayList
<delete id="deleteBatch" parameterType="java.util.ArrayList">
delete from product_info where p_id in
<foreach collection="array" item="pid" separator="," open="(" close=")">
#{pid}
</foreach>
</delete>
- 当我们 单参数List集合的时候,我们这个 <foreach> 写法也是不一样的。它必须是
collection 默认值为 list
,并且parameterType = java.util.List
<delete id="deleteBatch" parameterType="java.util.List">
delete from product_info where p_id in
<foreach collection="list" item="pid" separator="," open="(" close=")">
#{pid}
</foreach>
</delete>
缓存
Mybatis存在一级缓存和二级缓存,我们首先来看一下一级缓存,默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存(一级缓存无法关闭,只能调整),我们来看看下面这段代码:
public static void main(String[] args) throws InterruptedException {
try (SqlSession sqlSession = MybatisUtil.getSession(true)){
TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
Student student1 = testMapper.getStudentBySid(1);
Student student2 = testMapper.getStudentBySid(1);
System.out.println(student1 == student2);
}
}
我们发现,两次得到的是同一个Student对象,也就是说我们第二次查询并没有重新去构造对象,而是直接得到之前创建好的对象。
一级缓存,在进行DML操作后,会使得缓存失效,也就是说Mybatis知道我们对数据库里面的数据进行了修改,所以之前缓存的内容可能就不是当前数据库里面最新的内容了。还有一种情况就是,当前会话结束后,也会清理全部的缓存,因为已经不会再用到了。但是一定注意,一级缓存只针对于单个会话,多个会话之间不相通。
public static void main(String[] args) {
try (SqlSession sqlSession = MybatisUtil.getSession(true)){
TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
Student student2;
try(SqlSession sqlSession2 = MybatisUtil.getSession(true)){
TestMapper testMapper2 = sqlSession2.getMapper(TestMapper.class);
student2 = testMapper2.getStudentBySid(1);
}
Student student1 = testMapper.getStudentBySid(1);
System.out.println(student1 == student2);
}
}
注意:一个会话DML操作只会重置当前会话的缓存,不会重置其他会话的缓存,也就是说,其他会话缓存是不会更新的!
但是我们可能希望,缓存能够扩展到所有会话都能使用,因此我们可以通过二级缓存来实现,二级缓存默认是关闭状态,要开启二级缓存,我们需要在映射器XML文件中添加:
<mapper>
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"
/>
</mapper>
public static void main(String[] args) {
Student student;
try (SqlSession sqlSession = MybatisUtil.getSession(true)){
TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
student = testMapper.getStudentBySid(1);
}
try (SqlSession sqlSession2 = MybatisUtil.getSession(true)){
TestMapper testMapper2 = sqlSession2.getMapper(TestMapper.class);
Student student2 = testMapper2.getStudentBySid(1);
System.out.println(student2 == student);
}
}
那么如果我不希望某个方法开启缓存呢?我们可以添加useCache属性来关闭缓存:
<select id="getStudentBySid" resultType="Student" useCache="false">
select * from student where sid = #{sid}
</select>
我们也可以使用flushCache="false"在每次执行后都清空缓存,通过这这个我们还可以控制DML操作完成之后不清空缓存。
<select id="getStudentBySid" resultType="Student" flushCache="true">
select * from student where sid = #{sid}
</select>
添加了二级缓存之后,会先从二级缓存中查找数据,当二级缓存中没有时,才会从一级缓存中获取,当一级缓存中都还没有数据时,才会请求数据库!
二级缓存 => 一级缓存 => 数据库
⭐出现各自的缓存内容不同步的问题
public static void main(String[] args) throws InterruptedException {
try (SqlSession sqlSession = MybatisUtil.getSession(true)){
TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
while (true){
Thread.sleep(3000);
System.out.println(testMapper.getStudentBySid(1));
}
}
}
我们现在循环地每三秒读取一次,而在这个过程中,我们使用IDEA手动修改数据库中的数据,将1号同学的学号改成100,那么理想情况下,下一次读取将无法获取到小明,因为小明的学号已经发生变化了。
但是结果却是依然能够读取,并且sid并没有发生改变,这也证明了Mybatis的缓存在生效,因为我们是从外部进行修改,Mybatis不知道我们修改了数据,所以依然在使用缓存中的数据,但是这样很明显是不正确的,因此,如果存在多台服务器或者是多个程序都在使用Mybatis操作同一个数据库,并且都开启了缓存,需要解决这个问题,就得关闭Mybatis的二级缓存来尽量避免不同 SqlSession 之间的数据共享,以确保每个 SqlSession 都能获取最新的数据,侧面上来看,属于尽量的保证一致性:
<settings>
<setting name="cacheEnabled" value="false"/>
</settings>
<select id="getStudentBySid" resultType="Student" useCache="false" flushCache="true">
select * from student where sid = #{sid}
</select>
然后就需要实现缓存共用,也就是让所有的Mybatis都使用同一个缓存进行数据存取,在后面,我们会继续学习Redis、Ehcache、Memcache等缓存框架,通过使用这些工具,就能够很好地解决缓存一致性问题。