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 语句

  1. 解决 查询 关键字 不同 情况的 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等缓存框架,通过使用这些工具,就能够很好地解决缓存一致性问题。

posted @ 2024-01-13 09:15  小哞^同^学的技术博客  阅读(2)  评论(0编辑  收藏  举报