Mybatis学习笔记

MyBatis

一、框架简介

1.1 框架的概念

​ 框架是软件的半成品,完成了软件开发过程中的通用操作,程序员只需要很少或不需要进行加工就能实现特定的功能,从而简化开发过程,提高效率。

1.2 常用框架

  • MVC框架:简化了Servlet的开发步骤

    • Struts2
    • SpringMVC
  • 持久层框架:完成数据库操作的框架

    • apache DBUtils
    • Hibernate
    • Spring JPA
    • MyBatis
  • 平台框架:整合不同功能的框架

    • Spring

​ SSM - Spring SpringMVC MyBatis

​ SSH - Spring Struts2 Hibernate

1.3 MyBatis介绍

​ MyBatis是一个半自动的ORM框架:

半自动:因为Hibernate是一个全自动的框架

ORM:Object Relational Mapping,对象关系映射,将Java中的一个对象于数据表中的一行记录一一对应。

ORM框架提供了实体类于数据表的映射关系,通过映射文件的配置,实现对象的持久化。

  • MyBatis的前身是iBtis,是由apache基金维护的一个开源项目
  • 在2010年时,iBatis迁移到了(代码托管)Google Code,正式更名为MyBatis
  • 在2013年时,MyBatis迁移到了github
  • MyBatis的特点:
    • 支持自定义SQL,支持存储过程
    • 对原有的JDBC进行了封装,几乎消除了所有的JDBC代码(打开连接,加载执行SQL…),开发者只需要关注SQL
    • 支持XML和注解配置方式自动完成ORM操作,实现结果映射

二、MyBatis框架部署

​ 框架部署,就是将框架引入项目中

2.1 创建Maven项目

  • Java工程
  • Web工程

2.2 添加MyBatis依赖

  • pom.xml中添加依赖,以下两个依赖都需要
    • Mysql Driver
    • MyBatis
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.6</version>
</dependency>

2.3 创建MyBatis配置文件

​ 在maven项目的main文件夹下有一个resources文件夹,在此文件夹下新建一个mybatis的配置文件。可以先添加Mybatis配置文件模板。

<configuration>
    <!-- 在environments中配置数据库的连接信息 -->
    <!-- 在environments标签中可以定义多个environment标签,每个environment标签可以定义一套连接配置 -->
    <!-- default属性,指定使用哪一套配置 -->
    <environments default="development">
        <environment id="development">

            <!-- transactionManager标签用于配置数据库的管理方式 -->
            <transactionManager type="JDBC"></transactionManager>

            <!-- dataSource标签就是用来配置数据库连接信息的 -->
            <dataSource type="POOLED">
                <property name="driver" value="${database.driver}"/>
                <property name="url" value="${database.url}"/>
                <property name="username" value="${database.username}"/>
                <property name="password" value="${database.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource=""></mapper>
    </mappers>
</configuration>

三、MyBatis框架使用

​ 案例:学生信息数据库操作

3.1 创建数据表

create table tb_students(
	sid int primary key auto_increment,
	stu_num char(5) not null UNIQUE,
	stu_name VARCHAR(20) not null,
	stu_gender char(2) not null,
	stu_age int not null
)

3.2 创建实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Student {
    private int stuId;
    private String stuNum;
    private String stuName;
    private String stuGender;
    private String stuAge;
}

3.3 创建DAO接口,定义方法

public interface StudentDAO {
    public int insertStudent(Student s);
    public int deleteStudent(String stuNum);
}

3.4 创建接口映射文件

  • resources目录下新建名为mappers的文件夹
  • mappers中新建名为StudentMapper.xml的配置文件,需要根据模板
  • 在映射文件中对DAO中定义的方法进行实现
<!--映射文件要与相对应的mapper接口通过namespace属性进行关联-->
<mapper namespace="com.gsjt.dao.StudentDAO">

    <!--id为mapper类中对应方法名,resultType为定义的接收类型,一般为对应实体类-->
    <select id="" resultType="">

    </select>

    <!--id为mapper类中方法名-->
    <insert id="insertStudent">
        insert into tb_students(stu_num, stu_name, stu_gender, stu_age)
        values (#{stuNum}, #{stuName}, #stuGender, #{stuAge})
    </insert>

    <delete id="deleteStudent">
        delete from tb_students where stu_name = #{stuNum}
    </delete>

</mapper>

3.5 将映射文件添加到主配置文件

<mappers>
    <mapper resource="mappers/StudentMapper.xml"></mapper>
</mappers>

四、单元测试

public class StudentDAOTest {
    @Test
    public void insertStudent() {

        try {
            // 加载mybatis配置文件
            InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

            // 会话(连接)工厂,参数为连接信息
            SqlSessionFactory factory = builder.build(is);
            SqlSession sqlSession = factory.openSession();
            StudentDAO studentDAO = sqlSession.getMapper(StudentDAO.class);
            System.out.println(studentDAO);

            // 插入方法
            int i = studentDAO.insertStudent(new Student(1, "10002", "李四", "男", 22));

            // 手动提交的事务
            sqlSession.commit();
            System.out.println("插入操作返回的结果, " + i);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void deleteStudent() {
    }
}

五、MyBatis的增删改查

案例:学生信息的增删改查

5.1 添加

略,上一节已经演示

5.2 删除

​ 根据学号进行删除操作

  • StudentDAO中定义删除方法

  • StudentMapper.xml中对接口方法进行“实现”,使用deldete标签

  • 测试类中编写测试代码

    	@Test
        public void deleteStudent() {
            try {
                InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
                SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
                SqlSessionFactory factory = builder.build(in);
                SqlSession session = factory.openSession();
    
                StudentDAO studentDAO = session.getMapper(StudentDAO.class);
    
                int i = studentDAO.deleteStudent("10002");
                session.commit();
    
                System.out.println("删除操作的结果, " + i);
    
            }   catch (Exception e) {
                e.printStackTrace();
            }
        }
    

5.3 修改操作

​ 根据学号(主键)修改其他字段信息

  • 添加接口方法

    public int updateStudent(Student s);
    
  • 添加mapper映射

    	<update id="updateStudent">
            update tb_students set
                stu_name = #{stuName},
                stu_gender = #{stuGender},
                stu_age = #{stuAge}
            where
                stu_num = #{stuNum}
        </update>
    
  • 测试方法

    	@Test
        public void testUpdateStudent() {
            try {
                InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
                SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
                SqlSessionFactory factory = builder.build(in);
                SqlSession session = factory.openSession();
                StudentDAO studentDAO = session.getMapper(StudentDAO.class);
                int i = studentDAO.updateStudent(new Student(0, "10001", "赵六", "女", 26));
                session.commit();
                assertEquals(1, i);
            }   catch (Exception e) {
                e.printStackTrace();
            }
        }
    

5.4 查询所有

​ 需要注意查询出来的结果和普通Java类的映射关系,有两种写法。

注意:查询是不需要事务的,写不写无所谓。

  • 给查出来的字段起别名,并使用指定类名

    	<!-- resultType指定返回结果封装的实体类 -->
        <!-- resultSets指定当前操作发牛的集合类型类型,在接口中方法返回值有的情况下可以省略 -->
        <select id="searchAllStudents" resultType="com.gsjt.pojo.Student">
            select sid stuId, stu_num stuNum, stu_name stuName, stu_gender stuGender, stu_age stuAge
            from tb_students;
        </select>
    
  • 使用resultMap,推荐使用这种,可复用。

    	<!-- 用于定义ORM -->
        <resultMap id="studentMap" type="com.gsjt.pojo.Student">
            <id column="sid" property="stuId"/>
            <id column="stu_num" property="stuNum"/>
            <id column="stu_name" property="stuName"/>
            <id column="stu_gender" property="stuGender"/>
            <id column="stu_age" property="stuAge"/>
        </resultMap>
    
        <!-- resultMap用于引用一个实体映射关系 -->
        <select id="searchAllStudents" resultMap="studentMap">
            select sid, stu_num, stu_name, stu_gender, stu_age
            from tb_students;
        </select>
    

5.5 查询单条记录

	<select id="queryStudent" resultMap="studentMap">
        select sid, stu_num, stu_name, stu_gender, stu_age
        from tb_students
        where stu_num = #{param}
    </select>
	@Test
    public void testQueryStudent() {
        try {
            InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            SqlSessionFactory factory = builder.build(in);
            SqlSession session = factory.openSession();
            StudentDAO studentDAO = session.getMapper(StudentDAO.class);
            Student s = studentDAO.queryStudent("10001");
            assertNotNull(s);
            System.out.println(s.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

5.6 多参数查询

注意:在MyBatis进行条件查询的时候,

1、操作方法只有一个简单类型或者子字符串类型的参数,在mapper配置的时候可以直接通过#{}任意参数直接获取;

2、如果操作方法,有一个对象类型的参数,在mapper配置的时候可以直接使用#{attrname}获取属性的值(attrname必须是该参数对象的属性);

3、如果操作方法有一个map类型的参数,在Mapper配置中可以直接通过#{key}获取对应key的value;

4、操作方法有多个参数,使用@Param注解指定别名.

注意:如果DAO操作方法没有通过@Param指定参数别名,在SQL语句中可以通过arg0,arg1...或者param1, param2...来获取参数。

	public List<Student> listStudentsByPage(@Param("start") int start,
                                            @Param("pageSize") int pageSize);
	<select id="listStudentsByPage" resultMap="studentMap">
        select sid, stu_num, stu_name, stu_gender, stu_age
        from tb_students
        limit #{start}, #{pageSize}
    </select>
	@Test
    public void testListStudentByPage() {
        try {
            InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            SqlSessionFactory factory = builder.build(in);
            SqlSession session = factory.openSession();
            StudentDAO studentDAO = session.getMapper(StudentDAO.class);
            List<Student> s = studentDAO.listStudentsByPage(0, 10);
            assertNotNull(s);
            s.forEach(item -> System.out.println(item.toString()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

5.7 查询记录总数

	public int countStudents();

​ 通过resultMap指定当前的返回值类型为int,不然会报错

	<select id="countStudents" resultType="int">
        select count(1) from tb_students
    </select>

5.8 添加操作,回填自动生成的主键

​ 添加操作时,new了一个Student对象,当插入时,stuId应该是数据库中自增的那个主键,应该回填给这个student对象。

<!-- useGeneratedKeys是否需要回填,keyProperty设置回填属性 -->
<insert id="insertStudent" useGeneratedKeys="true" keyProperty="stuId">
    insert into tb_students(stu_num, stu_name, stu_gender, stu_age)
    values (#{stuNum}, #{stuName}, #{stuGender}, #{stuAge})
</insert>
  • useGeneratedKeys是否需要回填
  • keyProperty设置回填属性

六、mybatis工具类封装

public class MyBatisUtil {

    private static SqlSessionFactory factory;
    private static final ThreadLocal<SqlSession> local = new ThreadLocal<SqlSession>();

    static {
        try {
            InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
            factory = new SqlSessionFactoryBuilder().build(in);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static SqlSession getSqlSession() {
        SqlSession sqlSession = local.get();
        if (sqlSession == null) {
            sqlSession = factory.openSession();
            local.set(sqlSession);
        }
        return factory.openSession();
    }

    public static <T extends Object>T getMapper(Class<T> c) {
        SqlSession sqlSession = getSqlSession();
        return sqlSession.getMapper(c);
    }
}

七、事务管理

SqlSession对象

  • getMapper(DAO.class):获取Mapper(DAO接口的实例)
  • 事务管理:提供事务管理方法

7.1 手动提交事务

 	@Test
    public void deleteStudent() {
        SqlSession session = MyBatisUtil.getSqlSession();
        try {
            StudentDAO studentDAO = session.getMapper(StudentDAO.class);
            int i = studentDAO.deleteStudent("10002");
            session.commit();      // 提交
            System.out.println("删除操作的结果, " + i);
        }   catch (Exception e) {
            session.rollback();     // 回滚
            e.printStackTrace();
        }
    }

7.2 自动提交事务

factory.openSession()方法可以传一个Boolean类型的参数,表示是否自动提交事务。默认是false。

​ 但是此时如果在程序中有多个数据库操作,若使用自动提交,则第一个操作完成后就自动提交了。此时建议手动管理。

八、MyBatis主配置文件

​ mybatis-config.xml 是 MyBatis主配置文件

注意:配置文件中的标签必须有序,按照如下顺序:properties, settings, typeAliases, typeHandlers, objectFactory, objectWrapperFactory, plugins, environments, databaseIdProvider, mappers

8.1 properties标签

​ 用于设置键值对,或者加载属性文件。 <property name ="" value=""/>

  • 在resources文件夹下创建jdbc.properties文件,键值对配置如下:

    mysql_driver = com.mysql.jdbc.Driver
    mysql_url = jdbc:mysql://localhost:3306/testdb?characterEncoding=utf-8
    mysql_username = root
    mysql_password = 123456
    
  • 主配置文件如下,properties标签引入之后,里面可以根据键值对取出数据

    <configuration>
        <properties resource="jdbc.properties"></properties>
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"></transactionManager>
                <dataSource type="POOLED">
                    <property name="driver" value="${mysql_driver}"/>
                    <property name="url" value="${mysql_url}"/>
                    <property name="username" value="${mysql_username}"/>
                    <property name="password" value="${mysql_password}"/>
                </dataSource>
            </environment>
        </environments>
        <mappers>
            <mapper resource=""></mapper>
        </mappers>
    </configuration>
    

8.2 settings标签

​ 设置mybatis工作时的一些属性

<settings>
    <setting name="cacheEnable" value="true"/>    <!-- 二级缓存 -->
    <setting name="lazyLoadingEnable" value="true"/>    <!-- 延迟加载 -->
</settings>

8.3 typeAlias标签

<!-- 用于给实体类取别名,在映射文件中可以直接使用别名来替代实体类的全限定名 -->
<typeAliases>
    <typeAlias type="com.gsjt.pojo.Student" alias="Student"></typeAlias>
</typeAliases>

8.4 plugins标签

​ 用于配置mybatis插件,例如分页插件。

8.5 environments标签

​ 配置数据库连接信息

	<environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${mysql_driver}"/>
                <property name="url" value="${mysql_url}"/>
                <property name="username" value="${mysql_username}"/>
                <property name="password" value="${mysql_password}"/>
            </dataSource>
        </environment>
    </environments>
  • environment标签,用于配置一个单独的数据库连接信息
    • transactionManagertype属性有两个值可选:
      • JDBC:用JDBC方式自己在代码中进行事务管理
      • MANAGED:事务交给容器管理,连接不自己手动处理
    • dataSource标签,配置数据库连接信息,type属性的几个取值:
      • POOLED
      • UNPOOLED
      • JNDI

8.6 mappers标签

​ 用于载入项目中的映射配置(映射文件,DAO注解)

	<mappers>
        <mapper resource="mappers/StudentMapper.xml"></mapper>
    </mappers>	

九、mapper文件配置

9.1 MyBatis Mapper文件的初始化过程

image-20211214182256425

9.2 Mapper文件规范

  • mapper文件的根标签必须为 mappermapper有个属性namespace指定实现哪个接口

  • insert声明添加操作

    常用属性

    • id,绑定接口方法名
    • parameterType,用于指定接口中对应方法的参数类型(可省略)
    • useGeneratedKeys,添加操作是否需要主键回填
    • keyProperty,指定回填的主键绑定到对象的哪个属性
    • timeout,设置此操作的超时时间,如果不设置就一直等待

主键回填的另外一种方式:使用selectKey标签。

  • delete声明删除

  • update修改

  • select查询操作,属性最多的操作

    常用属性

    • resultType,指定当前sql执行返回的类型
    • resultMap,指定数据表和实体类之间的对应关系
    • useCache,是否开启二级缓存
    • id
    • timeout
    • parameterType
  • resultMap指定字段映射关系

    <!-- 用于定义ORM -->
    <resultMap id="studentMap" type="com.gsjt.pojo.Student">
        <id column="sid" property="stuId"/>
        <id column="stu_num" property="stuNum"/>
        <id column="stu_name" property="stuName"/>
        <id column="stu_gender" property="stuGender"/>
        <id column="stu_age" property="stuAge"/>
    </resultMap>
    
  • cache指定缓存属性配置

    <cache type="" size="" readOnly="false"/>
    

    缓存的配置详见缓存章节

  • sqlinclude标签,相当于服用,sql标签定义sql语句片段,include引入这个片段。

    <sql id="test">sid, stu_name, stu_gender</sql>
    
    <select id="" resultMap ="">
        select <include refid="test"> from tb_students
    </select>
    

十、分页插件

​ 分页插件是一个独立于MyBatis框架之外的第三方插件PageHelper

10.1 添加分页插件依赖

<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.2.0</version>
</dependency>

10.2 配置问价

​ 在mybatis的主配置文件中通过plugins标签进行配置

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>

10.3 使用

	@Test
    public void testPageHelper() {
        StudentDAO studentDAO = MyBatisUtil.getMapper(StudentDAO.class);

        PageHelper.startPage(2, 4);
        
        List<Student> students = studentDAO.listByPageHelper();
        
        PageInfo<Student> pageInfo = new PageInfo<Student>(students);
        
        students.forEach(item -> System.out.println(item.toString()));
        System.out.println("--------------------------------------------------");
        pageInfo.getList().forEach(item -> System.out.println(item.toString()));
    }

十一、关联映射

11.1 实体关系

​ 实体关系——数据实体,实体关系事知数据与数据之间的关系

​ 例如:用户和角色,房屋和漏洞,订单和商品

实体关系分为以下四种:

  • 一对一:人和身份证,学生和学号,用户基本信息和用户详情

    数据表关系:主键关联(用户表主键和详情主键相同,表示匹配的数据)

  • 一对多:班级和学生

  • 多对一:学生和班级

    数据表关系:在多的那一端添加外键和一的那一端主键关联

  • 多对多:用户和角色,学生和社团

    数据表关系:建立第三章关系表分别和原两张表的主键进行关联

11.2 创建web项目,引入mybatis

11.3 一对一关系

​ 实例:用户——详情

11.3.1 数据表
-- 用户信息表
create table users(
	user_id int primary key auto_increment,
    user_name varchar(20) not null unique,
    user_pwd varchar(20) not null,
    user_realname varchar(20) not null,
    user_img varchar(100) not null
);
-- 用户详情表
create table details(
	detail_id int primary key auto_increment,
    user_addr varchar(50) not null,
    user_tel char(11) not null,
    user_desc varchar(200),
    uid int not null unique,
    -- constraint users foreign key(uid) reference users(user_id)
);
11.3.2 创建实体类
/* User.java */
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private int userId;
    private String userName;
    private String userPwd;
    private String userRealName;
    private String userImg;

    // 内部对象
    private Detail detail;
}
/* Detail.java */
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Detail {
    private int detailId;
    private String userAddr;
    private String userTel;
    private String userDesc;
    private int userId;
}
11.3.3 插入

​ 插入时需要保证原子性,插入users表的同时也要插入details表。并且在逻辑上使用外键。需要插入users表的时候使用主键回填。

<sql id="allAttrs">
    user_name, user_pwd, user_realname, user_img
</sql>

<insert id="insertUser" useGeneratedKeys="true" keyProperty="userId">
    insert into users(<include refid="allAttrs"/>)
    values (#{userName}, #{userPwd}, #{userRealName}, #{userImg})
</insert>

<!-- detail的mapper -->
<sql id="allAttrs">
    user_addr, user_tel, user_desc, user_id
</sql>

<insert id="insertDetail">
    insert into details(<include refid="allAttrs"/>)
    values (#{userAddr}, #{userTel}, #{userDesc}, #{userId})
</insert>
@Test
public void testUserInsert() {
    User u = new User(0, "nickname", "wadwa", "赵六", "01.jpg", null);
    Detail d = new Detail(0, "武汉", "18600011125", "这是一条签名", 0);
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    try {
        UserDAO userDAO = sqlSession.getMapper(UserDAO.class);
        int i = userDAO.insertUser(u);
        System.out.println("新用户插入, " + i);

        d.setUserId(u.getUserId());		// 设置逻辑外键
        DetailDAO detailDAO = sqlSession.getMapper(DetailDAO.class);
        int j = detailDAO.insertDetail(d);
        System.out.println("详情插入, " + j);

        sqlSession.commit();	// 提交事务
    }   catch (Exception e) {
        e.printStackTrace();
        sqlSession.rollback();	// 回滚事务
    }
}
11.3.4 一对一关联查询

​ 难点在于,关联的映射,有两种映射方式

  • 表连接查询,将内部类属性直接映射

    <sql id="allAttrs">
        user_name, user_pwd, user_realname, user_img
    </sql>
    
    <resultMap id="userMap" type="com.gsjt.pojo.User">
        <id column="user_id" property="userId"/>
        <result column="user_name" property="userName"/>
        <result column="user_pwd" property="userPwd"/>
        <result column="user_realname" property="userRealName"/>
        <result column="user_img" property="userImg"/>
        <result column="detail_id" property="detail.detailId"/>
        <result column="user_addr" property="detail.userAddr"/>
        <result column="user_tel" property="detail.userTel"/>
        <result column="user_desc" property="detail.userDesc"/>
        <result column="user_id" property="detail.userId"/>
    </resultMap>
    
    <select id="queryUser" resultMap="userMap">
        select u.user_id, <include refid="allAttrs"/>, detail_id, user_addr, user_tel, user_desc
        from users u
        inner join details d
        on u.user_id = d.user_id
        where u.user_name = #{username};
    </select>
    
  • 使用子查询

    • 在另外一张表中使用查询

      <sql id="allAttrs">
          user_addr, user_tel, user_desc, user_id
      </sql>
      
      <resultMap id="detailMap" type="com.gsjt.pojo.Detail">
          <id column="detail_id" property="detailId"/>
          <result column="user_addr" property="userAddr"/>
          <result column="user_tel" property="userTel"/>
          <result column="user_desc" property="userDesc"/>
          <result column="user_id" property="userId"/>
      </resultMap>
      
      <select id="queryDetailByUid" resultMap="detailMap">
          select detail_id, <include refid="allAttrs"/>
          from details
          where user_id = #{userId}
      </select>
      
    • 在主表中使用子查询,借助assosiation标签

      <resultMap id="userMapUseSubQuery" type="com.gsjt.pojo.User">
          <id column="user_id" property="userId"/>
          <result column="user_name" property="userName"/>
          <result column="user_pwd" property="userPwd"/>
          <result column="user_realname" property="userRealName"/>
          <result column="user_img" property="userImg"/>
          <association property="detail" select="com.gsjt.dao.DetailDAO.queryDetailByUid" column="user_id"/>
      </resultMap>
      
      <select id="queryUserUseSubQuery" resultMap="userMapUseSubQuery">
          select user_id, <include refid="allAttrs"/>
                 from users
          where user_name = #{username}
      </select>
      

11.4 一对多关联

​ 案例:班级信息(1) —— 学生信息(n)11.4.1

11.4.1 创建表
create table classes(
	cid int primary key auto_increment,
	cname varchar(20) not null unique,
	cdesc varchar(100)
);

create table students(
	sid char(5) primary key,
	sname varchar(20) not null,
	sage int not null,
	scid int not null
);
11.4.2 实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Clazz {
    private int classId;
    private String className;
    private String classDesc;

    private List<Student> stus;
}

@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    private String studentId;
    private String stuName;
    private int stuAge;
    private int stuCid;
}
11.4.3 关联查询

​ 当查询一个班级的时候,要关联查询出来这个班级所有的学生

也有两种实现方式

  • 连接查询

    <resultMap id="classMap" type="Clazz">
        <id column="cid" property="classId"/>
        <result column="cname" property="className"/>
        <result column="cdesc" property="classDesc"/>
    
        <!-- Clazz对象的stus属性是一个List集合,需要使用collection标签 -->
        <!-- collection标签的property属性表示绑定到对象的哪个属性,ofType属性表示集合内的元素类型 -->
        <collection property="stus" ofType="Student">
            <result column="sid" property="stuId"/>
            <result column="sname" property="stuName"/>
            <result column="sage" property="stuAge"/>
        </collection>
    </resultMap>
    
    <select id="queryClass" resultMap="classMap">
        select cid, cname, cdesc, sid, sname, sage
        from classes c inner join students s
        on c.cid = s.scid
        where c.cid = #{classId};
    </select>
    
  • 子查询

    <resultMap id="classMap" type="Clazz">
        <id column="cid" property="classId"/>
        <result column="cname" property="className"/>
        <result column="cdesc" property="classDesc"/>
    
        <collection property="stus" select="com.gsjt.dao.StudentDAO.listStudentsByCid" column="cid"/></resultMap>
    
    <select id="queryClass" resultMap="classMap">
        select cid, cname, cdesc
        from classes
        where cid = #{classId}
    </select>
    

    同时也要配置子查询中的查询接口和mapper。

11.5 多对一关联

​ 实例:学生(n) —— 班级(1)

​ 当查询一个学生的时候,同时查询出对应的班级对象。

11.5.1 实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Clazz {
    private int classId;
    private String className;
    private String classDesc;
    // private List<Student> stus;
}

@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    private String stuId;
    private String stuName;
    private int stuAge;
    // private int stuCid;

    private Clazz clazz;
}
11.5.2 关联查询
  • 连接查询:于一对一关系查询差不多
  • 子查询:于一对一关系查询差不多

11.6 多对多关联

​ 案例:学生和课程

11.6.1 创建数据表

​ 其中需要创建第三章关系表

-- 学生表如上
-- 课程表
create table courses(
	cources_id int primary key auto_increment,
    cource_name varchar(20) not null
);

-- 选课信息表/成绩表
create table grades(
	sid char(5) not null,
    cid int not null,
    score int not null
);
11.6.2 关联查询

​ 查询学生时,同时查询学生选择的课程

​ 根绝课程编号查询课程时,同时查询选择这门课的学生

十二、动态SQL

​ 交友网站,电商平台都有筛选功能

​ Mybatis提供了动态SQL的配置方式来实现多条件查询

12.1 什么是动态SQL

​ 根据查询条件动态的完成SQL的拼接

12.2 标签

  • if:满足条件则拼接,否则不拼接

    <select id="findActiveBlogWithTitleLike"
         resultType="Blog">
      	SELECT * FROM BLOG
      	WHERE 1 = 1
      	<if test="title != null">
        	AND title like #{title}
      	</if>
        <if test="author != null and author.name != null">
        	AND author_name like #{author.name}
      	</if>
    </select>
    
  • choosewhenotherwise:类似于java中的switch,从多个条件中选择一个使用

    <select id="findActiveBlogLike"
         resultType="Blog">
      	SELECT * FROM BLOG WHERE 1 = 1
      	<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>
    
  • where标签:用于定义where语句

    <select id="findActiveBlogLike"
         resultType="Blog">
      	SELECT * FROM BLOG
      	<where>
        	<if test="state != null">
             	state = #{state}
        	</if>
        	<if test="title != null">
            	AND title like #{title}
        	</if>
        	<if test="author != null and author.name != null">
            	AND author_name like #{author.name}
        	</if>
      	</where>
    </select>
    

    where标签元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

  • trim:如果where标签定义的字句不满足要求,也可以自定义trim元素来定制where元素的功能。比如和where子句等价的自定义trim元素为:

    <trim prefix="WHERE" prefixOverrides="AND |OR ">
      ...
    </trim>
    

    prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有prefixOverrides属性中指定的内容,并且插入prefix属性中指定的内容。

  • set标签:用于动态更新语句。set元素可以动态的包含需要的列,忽略其他更新的列:

    <update id="updateAuthorIfNecessary">
      	update Author
        <set>
        	<if test="username != null">username=#{username},</if>
         	<if test="password != null">password=#{password},</if>
          	<if test="email != null">email=#{email},</if>
          	<if test="bio != null">bio=#{bio}</if>
        </set>
      	where id=#{id}
    </update>
    

    等价于:

    <trim prefix="SET" suffixOverrides=",">
      ...
    </trim>
    
  • forEach:集合遍历(在构建IN子句时常用)

    <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>
    
  • bind:允许创建一个变量,在表达式中动态绑定

    <select id="selectBlogsLike" resultType="Blog">
      <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
      SELECT * FROM BLOG
      WHERE title LIKE #{pattern}
    </select>
    
  • script:要在带注解的映射器接口类中使用动态的SQL,可以使用script元素

    @Update({"<script>",
             "update Author",
             "  <set>",
             "    <if test='username != null'>username=#{username},</if>",
             "    <if test='password != null'>password=#{password},</if>",
             "    <if test='email != null'>email=#{email},</if>",
             "    <if test='bio != null'>bio=#{bio}</if>",
             "  </set>",
             "where id=#{id}",
             "</script>"})
    void updateAuthorValues(Author author);
    

12.3 #{} 和 ${}

  • ${key}表示获取参数,先获取参数的值拼接到sql语句中,然后再执行该sql。可能引起SQL注入的问题;
  • #{key}表示获取参数,先完成sql语句的编译(预编译),预编译之后再将获取的参数设置到SQL中。即先编译成?占位符。这样可以避免SQL注入的问题。

注意:在使用${key}的时候,默认去参数时会当作是对象来取,传参的时候可以用参数对象或者HashMap,如果是普通的值则必须要声明参数类型,在select标签中要添加parameterType属性,而且在传参的时候必须加上@Param才能获取到,哪怕只有一个参数。

<select id="searchMember" parameterType="java.lang.String" resultMap="...">
    select * from ...
    where name like '%${keyword}%'		
</select>
pubilc List<...> ambigousSearch(@Param("keyword") String keyword);

十三、MyBatis日志配置

​ MyBatis作为一个封装好的ORM框架,其运行过程没有办法进行跟踪,为了当开发者了解其执行流程及每个执行步骤完成所有的工作,MyBatis框架本身集成了log4j日志框架,对运行的过程进行记录跟踪。我们只需要对MyBatis进行相关的日志配置,就可以看到MyBatis运行过程中的日志信息。

13.1 添加日志框架依赖

<!-- log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

13.2 添加日志配置文件

  • resources目录下创建名为log4j.properties的文件,必须在该文件夹下创建该名称的文件,因为MyBatis自动加载。

  • log4j.properties文件中配置日志输出的方式。

    ​ 这是MyBatis官方的配置

    # 声明日志的输出级别及输出方式
    log4j.rootLogger=DEBUG,stdout
    log4j.logger.org,mybatis.example.BlogMapper=TRACE
    # Console output...
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    # 定义打印格式 %t是线程名称  %5p是日志级别
    log4j.appender.stdout.layout.ConversionPattern=[%t] %5p - %n%m
    

13.3 日志级别

​ 在使用日志框架进行输出日志信息的时候,会根据输出的日志信息的重要程度分为5个级别。由宽松到严格。

日志级别说明
DEBUG输出调试信息
INFO输出提示性信息
WARN输出警告信息
ERROR一般性错误信息
FATAL致命性错误信息

十四、数据库连接池配置-整合Druid

连接池:连接池就是用于存储连接对象的一个容器,而容器就是一个集合,且必须是线程安全的,即两个线程不能拿到同一个连接对象。同时还要具备队列的特性:先进先出原则。

使用连接池的好处:避免频繁创建和关闭数据库连接造成的开销,节省系统资源。

​ MyBatis作为一个ORM框架,在进行数据库操作的时候是需要和数据库进行连接的,MyBatis支持基于数据库的连接池的创建方式。

​ 当我们配置MyBatis数据源的时候,只配置了dataSource标签的type属性值为POOLED时,就可以使用MyBatis内置的连接池管理连接。

如果我们想要使用第三方的数据库连接池就需要自己配置。

14.1 常见的连接池

  • JDBC

  • DBCP

  • C3P0

  • Druid:性能比较好,提供了比较便捷的监控系统,在企业中使用的比较多,阿里开源

  • HiKari:性能最好

14.2 添加依赖

<!-- druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.8</version>
</dependency>

14.3 创建Druid连接池工厂

​ 创建一个类继承PooledDataSourceFactory,在无参构造器中设置dataSourceDruidDataSource

public class DruidDataSourceFactory extends PooledDataSourceFactory {
    public DruidDataSourceFactory() {
        this.dataSource = new DruidDataSource();
    }
}

14.4 将Druid连接池工厂配置给MyBatis数据源

​ 在MyBatis主配置文件中,使用DruidDataSourceFactory

<environment id="DruidTest">
    <transactionManager type="JDBC"></transactionManager>
    <!-- POOLED使用的是MyBatis内部的连接池 -->
    <!-- MyBatis需要一个连接池工厂,这个工厂可以产生数据库连接池PooledDataSourceFactory -->
    <!-- DruidDataSourceFactory继承自PooledDataSourceFactory -->
    <dataSource type="com.gsjt.utils.DruidDataSourceFactory">
        <property name="driverClass" value="${mysql_driver}"/>
        <property name="jdbcUrl" value="${mysql_url}"/>
        <property name="username" value="${mysql_username}"/>
        <property name="password" value="${mysql_password}"/>
    </dataSource>
</environment>

​ 同时,Druid需要的参数是driverClassjdbcUrl这和POOLED模式不一样。

十五、MyBatis缓存

​ MyBatis是基于JDBC的封装,使得数据库操作更加便捷,MyBatis除了对JDBC进行操作步骤进行封装之外也对其性能进行了优化:

  • 在MyBatis中引入了缓存机制,用于提升MyBatis的检索效率
  • 在MyBatis中引入了延迟加载机制,用于减少对数据库的不必要的访问

15.1 缓存的工作原理

  • 检查缓存中是否存在要查询的数据;
  • 如果存在,则直接从缓存获取数据并返回,减少了访问数据库的次数,大大提升效率;
  • 如果不存在则从数据库中查询,查询之后将数据返回,同时存储到缓存中,一共下次查询使用。

15.2 MyBatis中的缓存

​ MyBatis中的缓存分为一级缓存和二级缓存

15.2.1 一级缓存

​ 一级缓存也叫做SqlSession级缓存,为每个SqlSession单独分配缓存内存,无需手动开启即可直接使用;多个SqlSession的缓存是不共享的

一级缓存的特性

  • 如果多次查询使用的是同一个SqlSession对象,则第一次查询之后数据会存放到缓存,后续的查询则直接访问缓存;
  • 如果第一次查询之后,对查询出来的对象进行修改,此修改会影响到缓存,第二次查询会直接访问缓存,造成查询出来的结果于数据库不一致。
  • 当想要跳过缓存直接查询数据库的时候,可以通过sqlSession.clearCache()来清除当前SqlSession对象的缓存。
  • 如果第一次查询之后,第二次查询之前使用当前的sqlSession进行更新,则缓存失效,第二次查询直接访问数据库。

存在的问题

  • 第一次查询之后,进行了修改操作,数据库已经被修改,但是第二次查询的时候依然显示修改前的数据
    • 分析:修改和查询不是同一个线程,因此使用不同的dao对象(使用了不同的SqlSession),因此修改不会导致查询操作的缓存失效;
    • 解决思路有两种
      • 让查询和修改使用相同的SqlSession对象(不太合理);
      • 让每次进行查询操作之后清空缓存
15.2.2 二级缓存

​ 二级缓存也称为SqlSessionFactory级的缓存。通过同一个factory对象获取的SqlSession可以共享二级缓存;在应用服务器中SqlSessionFactory是 单例的,因此二级缓存的数据可以全局共享。

二级缓存特性

  • 二级缓存默认没有开启,需要在mybatis-config.xml中的setting标签中进行开启,

    <settings>
    	<setting name="cacheEnabled" value="true"></setting>
    </settings>
    

    另外在mapper文件内只要使用了chche标签则,则开启了二级缓存。这个标签有一些参数,eviction设置缓存回收策略,取值LRU(默认),FIFO,SOFT,WEAK。flushInterval设置刷新间隔,默认是不清空。readOnly是否只读。size指定大小。type指定自定义缓存的全类名,实现Cache接口即可。

    <cache></cache>
    
  • 二级缓存只能缓存实现了序列化接口的对象

15.2.3 缓存查询数据
  • 先判断二级缓存是否开启,如果没开启,再判断一级缓存是否开启,如果没开启,直接查数据库

  • 如果一级缓存关闭,即使二级缓存开启也没有数据,因为二级缓存的数据从一级缓存获取

  • 一般不会关闭一级缓存

  • 二级缓存默认不开启

  • 如果二级缓存关闭,直接判断一级缓存是否有数据,如果没有就查数据库

  • 如果二级缓存开启,先判断二级缓存有没有数据,如果有就直接返回;如果没有,就查询一级缓存,如果有就返回,没有就查询数据库;

​ 综上所述:先查二级缓存,再查一级缓存,再查数据库;即使在一个sqlSession中,也会先查二级缓存;一个namespace中的查询更是如此;

十六、延迟加载

​ 延迟加载是指,如果在MyBatis执行了子查询(至少查询两次以上),默认只执行第一次查询,当用到子查询的查询结果时,才会触发子查询的执行。如果没有使用子查询的结果,则子查询不会进行。

​ 需要配置MyBatis的主配置文件

<settings>  
   <setting name="lazyLoadingEnabled" value="true"/><!--延迟加载/懒加载-->  
   <setting name="aggressiveLazyLoading" value="false"/><!--积极加载/预加载-->  
</settings>  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值