Mybatis学习笔记

文章目录

Mybatis

1. Mybatis 简介

1.1 什么是Mybatis

在这里插入图片描述

    MyBatis 本是 apache 的一个开源项目 iBatis, 2010 年这个项目由 apache software foundation 迁移到了 google code,并且改名为MyBatis 。2013 年 11 月迁移到 Github。iBatis 一词来源于“internet”和“abatis”的组合,是一个基于 Java 的持久层框架。iBATIS 提供的持久层框架包括 SQL Maps 和 Data Access Objects(DAO)。

    Mybatis是基于java的持久层框架,它的内部封装了JDBC,让开发人员只需要关注SQL语句本身,不需要花费精力在驱动的加载、连接的创建、Statement的创建等复杂的过程。Mybatis通过XML或注解的方式将要执行的各种的statement配置起来,并通过java对象和statement中的sql的动态参数进行映射生成最终执行的SQL语句,最后由mybatis框架执行SQL,并将结果直接映射为java对象。

    采用了ORM(Object Relationship Mapping,对象关系映射)思想解决了实体类和数据库表映射的问题。对JDBC进行了封装,屏蔽了JDBC API底层的访问细节,避免我们与jdbc的api打交道,就能完成对数据的持久化操作

  1. 持久化

    将程序数据在持久状态和瞬时状态间转换的机制, 即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内存中的对象存储在数据库中,或者存储在磁盘文件中、XML数据文件中等等。JDBC就是一种持久化机制,文件IO也是一种持久化机制。

  2. 持久层

    完成持久化工作的代码块——DAO层 (Data Access Object,数据访问对象),在我们的系统架构中,应该有一个相对独立的逻辑层面,专著于数据持久化逻辑的实现。


1.2 回顾JDBC

public class TestJDBC {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        
        try {
            //加载驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            String url="jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT";
            //获取连接
            conn= DriverManager.getConnection(url,"root","root");
            //SQL语句
            String sql="select * from team;";
            ps=conn.prepareStatement(sql);
            //执行查询
            rs = ps.executeQuery();
            //遍历结果集
            List<Team> list=new ArrayList<>();
            while (rs.next()){
                Team team=new Team();
                team.setTeamName(rs.getString("teamName"));
                team.setTeamId(rs.getInt("teamId"));
                team.setCreateTime(rs.getDate("createTime"));
                team.setLocation(rs.getString("location"));
                list.add(team);
            } 
            list.forEach(team -> System.out.println(team));
        }catch (Exception e){
        	e.printStackTrace();
        }finally {
            try {
                //关闭资源
                if (rs != null){
                    rs.close();
                } 
                if (ps != null){
                    ps.close();
                } 
                if (conn != null){
                    conn.close();
                }
        	} catch (Exception e) {
        		e.printStackTrace();
        	}
        }
    }
}

1.3 Mybatis的优点

  • 提高效率

    Mybatis是一个半自动化的ORM框架 (Object Relationship Mapping),帮助程序猿将数据存入数据库中 , 和从数据库中取数据。传统的jdbc操作 , 有很多重复代码块 .比如 : 数据取出时的封装 , 数据库的建立连接等等,这些频繁操作容易造成资源的浪费从而影响系统的性能,通过框架可以减少重复代码,提高开发效率

  • 简单易学

    本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件就可以了,易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。

  • 灵活

    mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。

  • 解除sql与程序代码的耦合

    通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。

  • 提供xml标签,支持编写动态sql

    SQL语句编写在代码中,硬编码造成代码不容易维护,实际应用中SQL语句变化的可能性比较大,一旦变动就需要改变java类。 使用preparedStatement的时候传递参数使用占位符,也存在硬编码,因为SQL语句变化,必须修改源码。对结果集的解析中也存在硬编码。

  • 使用的人多

    出现问题时可以集思广益,快速解决问题。



2. Myabtis入门案例

在这里插入图片描述

2.1 数据库、表的创建

CREATE TABLE `team` (
    `teamId` int NOT NULL AUTO_INCREMENT COMMENT '球队ID',
    `teamName` varchar(50) DEFAULT NULL COMMENT '球队名称',
    `location` varchar(50) DEFAULT NULL COMMENT '球队位置',
    `createTime` date DEFAULT NULL COMMENT '球队建立时间',
    PRIMARY KEY (`teamId`)
) ENGINE=InnoDB AUTO_INCREMENT=1003 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

2.2 创建Maven项目并配置POM文件

<dependencies>
    <!--Mybatis依赖-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.6</version>
    </dependency>
    <!--JDBC依赖-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.23</version>
    </dependency>
    <!--Junit依赖-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.0</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
</build>

2.3 配置Mybatis的核心配置文件

    我们这里创建的核心配置文件名为mybatis-config.xml。

参考中文的网站: https://mybatis.org/mybatis-3/zh/getting-started.html

<?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>
    <!--配置 mybatis 环境-->
    <environments default="development">
        <!--id:数据源的名称-->
        <environment id="development">
            <!--事务类型:使用 JDBC 事务,使用 Connection 的提交和回滚-->
            <transactionManager type="JDBC"></transactionManager>
            <!--
                数据源 dataSource:创建数据库 Connection 对象
                type: POOLED 使用数据库的连接池
            -->
            <dataSource type="POOLED">
                <!--连接数据库的四大参数-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/><!--注意数据库版本使用的是MySQL8,如果是mysql5的话,driver和url都不一样,参考学过的JDBC-->
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false&amp;serverTimezone=GMT"/>
                <property name="username" value="root"/>
                <property name="password" value="lijinghua"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

2.4 创建实体类

    实体类中的属性必须与表中的列名保持一致。

public class Team {
    private Integer teamId;
    private String teamName;
    private String location;
    private Date createTime;

    public Team() {
    }

    @Override
    public String toString() {
        return "Team{" +
                "teamId=" + teamId +
                ", teamName='" + teamName + '\'' +
                ", location='" + location + '\'' +
                ", createTime=" + createTime +
                '}';
    }
    
    //getter && setter
}

2.5 创建Dao层接口

    Mapper 动态代理方式无需程序员实现 Dao 接口。接口是由 MyBatis 结合映射文件,利用JDK的动态代理实现的

附上Mybatis学友的详细说明(MyBatis框架是如何去执行SQL语句):https://blog.csdn.net/xcxy2015/article/details/80652833

public interface TeamDao {

    /**
     * 查询全部球队
     * @return 球队的List集合
     */
    public abstract List<Team> queryAll();
}

2.6 编写Mybatis工具类

    对获取数据库连接操作进行封装。

public class MybatisUtil {
    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            String resource = "mybatis-config.xml";
            //1、读取mybatis的配置文件
            InputStream inputStream = Resources.getResourceAsStream(resource);
            //2、创建SqlSessionFactory对象,目的是获取sqlSession--根据图纸创建工厂
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //获取SqlSession连接
    public static SqlSession getSession() {
        return sqlSessionFactory.openSession();
    }
}

2.7 编写ORM映射文件

    示例:我们是针对实体类Team.java和表Team进行ORM映射。

Mybatis框架中,ORM映射是针对SQL语句进行,Mybatis框架将SQL语句抽取到了XML中。

注意:我们可以把这个映射文件放在与对应接口的同一个包中;也可以放在resources目录下(一级级去创建),但是!!!需要和接口的目录一致!!!

在这里插入图片描述

<?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.lijinghua.dao.TeamDao">
    <!--
		id="自定义名称,id不能重复;与dao中的方法名称对应"
		resultType="使用的要求:实体类中的属性名与表中的列名一致"
	-->
    <select id="queryAll" resultType="com.lijinghua.pojo.Team">
        select * from team;
    </select>
</mapper>

2.8 配置映射文件的扫描位置

    在pom.xml文件配置映射文件的扫描路径(Maven静态资源过滤问题),不然默认去查resources目录下了,容易出现找不到的问题。

<resources>
    <resource>
        <directory>src/main/java</directory><!--所在的目录-->
        <includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
            <include>**/*.properties</include>
            <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
    </resource>
</resources>

2.9 注册映射文件

    在Mybatis核心配置文件中注册映射文件。

<?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>
    <!--配置 mybatis 环境-->
    <environments default="development">
		……
    </environments>

    <!--注册映射文件-->
    <mappers>
        <mapper resource="com/lijinghua/dao/TeamMapper.xml"/>
    </mappers>
</configuration>

2.10 测试代码

@Test
public void queryAll() {
    SqlSession sqlSession = null;
    try {
        sqlSession = MybatisUtil.getSession();//获取数据库连接

        
        
        //方法一:
        //List<Team> teams = sqlSession.selectList("com.lijinghua.dao.TeamDao.queryAll");//执行sql

        //方法二:
        TeamDao mapper = sqlSession.getMapper(TeamDao.class);//获取指定接口的代理对象 动态代理
        List<Team> teams = mapper.queryAll();//相当于执行了sql,并处理了结果集,最后返回

        for (Team team : teams) {
            System.out.println(team);
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        sqlSession.close();
    }
}

2.11 测试结果

在这里插入图片描述



3.Mybatis的CRUD操作

    在进行CRUD的操作之前,我们需要先简单理解映射文件中下面三个参数的意义作为铺垫!

  • id
    命名空间中唯一的标识符,接口中的方法名与映射文件中的SQL语句id 一一对应。
  • parameterType
    传入SQL语句的参数类型(类型必须是完全限定名或别名),不写也可以,mybatis会自动识别,但是为了规范,代码可读性,还是写上好。【万能的Map,可以多尝试使用】
  • resultType
    SQL语句返回值类型(如果返回的是集合,写得是集合中的元素类型),实体类中的属性和表中的列名一致。【完整的类名或者别名】

注意:增、删、改需要提交事务,可以手动提交也可以在获取连接的时候设置成自动提交(重载方法),不写的话不会提交到数据库,因为mybatis核心配置文件中配置的JDBC事务管理默认是false,默认不提交。


3.1 增加insert

    我们一般使用insert标签进行插入操作,它的配置和select标签差不多! 以添加一个球队作为示例。

  1. 在TeamDao中添加对应方法;

    /**
    * 增加一个球队
    * @param team 要增加的球队信息
    * @return 0增加失败 >0增加成功
    */
    public abstract int insertTeam(Team team);
    
  2. 在TeamMapper映射文件中添加insert的sql语句;

    <!--如果传递的参数是对象,那么#{值}中(其实就是占位符?)的值必须和实体类Team中的参数名一致,表示从传递过来的参数对象中取出值-->
    <insert id="insertTeam" parameterType="com.lijinghua.pojo.Team">
        insert into team (teamName,location,createTime) values (#{teamName},#{location},#{createTime});
    </insert>
    
  3. 编写测试代码;

    @Test
    public void insertTeam() {
        SqlSession sqlSession = null;
        try {
            sqlSession = MybatisUtil.getSession();
    
            Team team = new Team(7777, "李京桦的球队", "上海", new Date());
    
            //方法一:
            //int result = sqlSession.insert("com.lijinghua.dao.TeamDao.insertTeam",team);
    
            //方法二:
            TeamDao mapper = sqlSession.getMapper(TeamDao.class);
            int result = mapper.insertTeam(team);
    
            sqlSession.commit(); //手动提交事务
            
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }
    }
    
  4. 测试结果。

    在这里插入图片描述

注:如果执行插入的时候,id设置成了自增(比如我们这里的teamId),那么怎么在获取呢?

<insert id="insertTeam" parameterType="com.lijinghua.pojo.Team">
    <!--
    	keyProperty="表示新增的id值赋值到哪里?(传入的Team中的teamId属性)"
		order="AFTER/BEFORE" 在insert执行之前还是之后
    	resultType="返回值的类型"
    -->
    <selectKey keyProperty="teamId" order="AFTER" resultType="java.lang.Integer">
        select LAST_INSERT_ID()
        <!--如果是字符串:select uuid() 表示插入数据之前(配合BEFORE)获取36位字符串作为id放入属性-->
    </selectKey>
    insert into team (teamName,location,createTime) values (#{teamName},#{location},#{createTime});
</insert>

3.2 删除delete

    我们一般使用delete标签进行插入操作,它的配置和select标签差不多! 以删除一个球队作为示例。

  1. 在TeamDao中添加对应方法;

    /**
    * 删除一个球队
    *
    * @param id 要删除的球队id
    * @return 0删除失败 >0删除成功
    */
    public abstract int deleteTeamById(int id);
    
  2. 在TeamMapper映射文件中添加delete的sql语句;

    <delete id="deleteTeamById" parameterType="java.lang.Integer">
        delete from team where teamId=#{id};
    </delete>
    
  3. 编写测试代码;

    @Test
    public void deleteTeamById() {
        SqlSession sqlSession = null;
        try {
            sqlSession = MybatisUtil.getSession();
    
            //方法一:
            //int result = sqlSession.update("com.lijinghua.dao.TeamDao.deleteTeamById",1117);
    
            //方法二:
            TeamDao mapper = sqlSession.getMapper(TeamDao.class);
            int result = mapper.deleteTeamById(1116);
    
            sqlSession.commit(); //提交事务,重点!不写的话不会提交到数据库
    
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }
    }
    
  4. 测试结果。

    在这里插入图片描述


3.3 修改update

    我们一般使用update标签进行修改操作,它的配置和select标签差不多! 以修改一个球队的信息作为示例。

  1. 在TeamDao中添加对应方法;

    /**
    * 修改一个球队的信息
    *
    * @param team 要修改的球队信息
    * @return 0修改失败 >0修改成功
    */
    public abstract int updateTeamById(Team team);
    
  2. 在TeamMapper映射文件中添加update的sql语句;

    <!--如果传递的参数是对象,那么#{值}中(其实就是占位符?)的值必须和实体类Team中的参数名一致,表示从传递过来的参数对象中取出值-->
    <update id="updateTeamById" parameterType="com.lijinghua.pojo.Team">
        update team set teamName=#{teamName},location=#{location} where teamId=#{teamId}
    </update>
    
  3. 编写测试代码;

    @Test
    public void updateTeamById() {
        SqlSession sqlSession = null;
        try {
            sqlSession = MybatisUtil.getSession();
    
            Team team = new Team(1116, "李京桦的球队", "哈尔滨", new Date());
    
            //方法一:
            int result = sqlSession.update("com.lijinghua.dao.TeamDao.updateTeamById",team);
    
            //方法二:
            //TeamDao mapper = sqlSession.getMapper(TeamDao.class);
            //int result = mapper.updateTeamById(team);
    
            sqlSession.commit(); //提交事务,重点!不写的话不会提交到数据库
    
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }
    }
    
  4. 测试结果。

    在这里插入图片描述


3.4 查询select

    在第2节中演示了查询全部球队的案例,现在以根据球队id进行查询作为示例。

  1. 在TeamDao中添加对应方法;

    /**
    * 根据球队id查询球队
    * @param id 球队id
    * @return 球队的查询结果
    */
    public abstract Team queryTeamById(int id);
    
  2. 在TeamMapper映射文件中添加Select的sql语句;

    <!--#{id}表示参数 id-自定义,因为只有一个参数,只需要符合命名规范即可,没有实际对应意义-->
    <select id="queryTeamById" parameterType="java.lang.Integer" resultType="com.lijinghua.pojo.Team">
        select * from team where teamId= #{id};
    </select>
    
  3. 编写测试代码;

    @Test
    public void queryTeamById() {
        SqlSession sqlSession = null;
        try {
            sqlSession = MybatisUtil.getSession();
    
            TeamDao mapper = sqlSession.getMapper(TeamDao.class);
            Team team = mapper.queryTeamById(1001);
    
            System.out.println(team);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }
    }
    
  4. 测试结果。

    在这里插入图片描述


3.5 输入映射

3.5.1 @Param注解

    之前都是写得一个参数,那如果是传入了多个参数呢?

    在接口方法的参数前加 @Param属性,Sql语句编写的时候,直接取@Param中设置的值即可,不需要单独设置参数类型。

//通过密码和名字查询用户
User selectUserByNP(@Param("username") String username,@Param("pwd") String pwd);

/*
<select id="selectUserByNP" resultType="com.lijinghua.pojo.User">
	select * from user where name = #{username} and pwd = #{pwd}
</select>
*/

注:也可以不用这个注解,而使用下标索引的方式(难记 还是用注解方便、可读性高):

select * from team where teamId >=#{param1} and teamId &lt;=#{param2};  

注意点1:

  mybatis3.3版本之前:可以直接写#{0} #{1}

  从mybatis3.4开始:#{arg0} #{arg1}... 或者是 #{param1} #{param2}...      
  (人家就叫arg或者param这个不是参数名!!! 这俩效果一样,就是下标的起始不一样)

注意点2:

  sql语句中不能使用小于号,使用转移符号替换 &lt; 
  大于号没有限制,也可以使用转义符号替换 &gt;

3.5.2 万能的Map

    在接口方法中,参数直接传递Map,编写sql语句的时候,需要传递参数类型,参数类型为map。

//接口中定义方法:通过密码和名字查询用户
User selectUserByNP2(Map<String,Object> map)

//映射文件中配置sql语句
/*
<select id="selectUserByNP2" parameterType="map" resultType="com.lijinghua.pojo.User">
	select * from user where name = #{username} and pwd = #{pwd}
</select>
*/

//测试
Map<String, Object> map = new HashMap<String, Object>();
map.put("username","小明");
map.put("pwd","123456");
User user = mapper.selectUserByNP2(map);

3.5.3 实体类

    与map传递多个参数类似,要求映射文件中的参数占位符必须和pojo类中的属性完全一致,我们在前面以及那个演示过了,这里就不多说了。


3.6 #{}与${}的区别

  • #{}

    表示一个占位符,通知Mybatis 使用实际的参数值代替。并使用 PrepareStatement 对象执行 sql 语句, #{…}代替sql 语句的“?”。这个是Mybatis 中的首选做法,安全迅速。

  • ${}

    表示字符串原样替换,通知Mybatis 使用美元符包含的“字符串”替换所在位置。使用 Statement或者PreparedStatement 把 sql 语句和${}的内容连接起来。一般用在替换表名、列名、不同列排序等操作

    <!--接口方法: -->
    List<Team> queryByFiled(@Param("column") String column,@Param("columnValue") String columnValue);
    
    <select id="queryByFiled" resultType="com.lijinghua.pojo.Team">
    	select * from team where ${column}=#{columnValue};
    </select>
    
    <!--测试:
        System.out.println("根据球队位置查询:");
        List<Team> teams2 = teamMapper.queryByFiled("location","洛杉矶");
        teams2.forEach(team -> System.out.println(team));
    	
    	变成: select * from team where location="洛杉矶";
    -->
    

3.7 输出映射

3.7.1 resultType

    执行 sql 得到 ResultSet 转换的类型,使用类型的完全限定名或别名

注意:

  • resultType 和 resultMap,不能同时使用

  • 如果要指定返回值类型为基本类型

    只有返回的结果是单行单列的时候才可以。如果是单行多列,不会报错,但是会取不到后面的列的值;如果返回多行会报异常 TooManyResultsException。

    解决方法:把这一行的数据当作是一个整体,用集合类型去处理!一般使用的是Map<Object,Object>

    • 单行多列

      //单行多列:Map 作为接口返回值,sql 语句的查询结果最多只能有一条记录。大于一条记录会抛出TooManyResultsException异常
      
      //接口方法
      Map<Object,Object> queryTwoColumn(int teamId);
      
      //sql语句
      <select id="queryTwoColumn" resultType="java.util.HashMap">
      	select teamName,location from team where teamId=#{id}
      </select>
      
    • 多行多列

      //多行多列:使用List<Map<Object,Object>>.
              
      //接口方法
      List<Map<Object,Object>> queryTwoColumnList();
              
      //sql语句
      <select id="queryTwoColumnList" resultType="java.util.HashMap">
      	select teamName,location from team
      </select>
      

      需要注意的是,这里针对基本类型的单行多列和多行多列的解决方法并不正规,阿里巴巴开发手册中明确提到,不允许直接拿HashMap和HashTable作为查询结构集的输出!!!

  • 如果返回的是具体的实体类类型

    那么就直接写实体类类型即可。

    自动映射:mybatis会根据这些查询的列名(会将列名转化为小写,数据库不区分大小写) , 去对应的实体类中查找相应列名的set方法设值,如果找不到就会返回为空!!

    //如何解决这种找不到映射的问题?这个问题可以用起别名解决!! 别名和实体类中的属性对应!
    <select id="selectUserById" resultType="User">
    	select id, name, pwd as password from user where id = #{id}
    </select>
    
  • 如果返回的是集合

    设置的是集合中存的元素的类型,而不是集合本身。


3.7.2 resultMap

    resultMap 可以自定义 sql 的结果和 java 对象属性的映射关系。更灵活的把列值赋值给指定属性。常用在列名和 java 对象属性名不一样的情况

记得前面3.7.1小节的注中也提到了这个找不到映射的问题,给了一个起别名的解决方案,这是第二种解决方式(相当于自定义版的手动映射,前面是Mybatis帮我们直接自动映射到底了),也很常用!

<select id="queryTeamById" parameterType="java.lang.Integer" resultMap="baseResultMap">
        select * from team where teamId= #{id};
</select>

<!--创建resultMap:相当于自己编写表中的列与实体类中的属性的映射
        id:resultMap的名称,要求唯一
        type:期待要映射为java的类型
    -->
<resultMap id="baseResultMap" type="com.lijinghua.pojo.Team">
    <!--一般主键列用id,其余列用result
        column:表示数据库表中的列名,不区分大小写
        property:表示实体类中的对应的属性名,区分大小写
        javaType:实体类中的对应的属性的类型,可以省略,mybatis会自己推断
        jdbcType="数据库中的类型column的类型" 一般省略
        -->
    <id column="teamId" property="teamId" javaType="java.lang.Integer" ></id>
    <result column="teamName" property="teamName" javaType="java.lang.String"></result>
    <result column="location" property="location" javaType="java.lang.String"></result>
    <result column="createTime" property="createTime" javaType="java.util.Date"></result>
</resultMap>

​ 当然只解决一对一的映射关系是不够的,数据库中,存在一对多,多对一的情况,我们之后会使用到一些高级的结果集映射(association、collection),我们将在之后的第七节讲解。



3.8 总结

  1. 所有的增删改操作都需要提交事务。
  2. 接口所有的普通参数,尽量都写上@Param参数,尤其是多个参数时,必须写上。
  3. 有时候根据业务的需求,可以考虑使用map传递参数(参数过多比较方便,参数少也可以一个个写)。
  4. 为了规范操作,在SQL的配置文件中,我们尽量将parameterType和resultType都写上。



4. Mybatis配置解析

4.1核心配置文件

    在我们的入门案例中,我们构建了mabatis-config.xml。MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。能配置的部分内容如下:

configuration(配置)
    properties(属性)
    settings(设置)
    typeAliases(类型别名)
    typeHandlers(类型处理器)
    objectFactory(对象工厂)
    plugins(插件)
    environments(环境配置)
    	environment(环境变量)
    		transactionManager(事务管理器)
    		dataSource(数据源)
	databaseIdProvider(数据库厂商标识)
    mappers(映射器)
<!-- 注意元素节点的顺序!顺序不对会报错 -->

    这么多怎么记呢?有没有参考的地方?

在这里插入图片描述

在这里插入图片描述



4.2 environments元素

    environments元素内配置了MyBatis的多套运行环境,将SQL映射到多个不同的数据库上,必须指定其中一个为默认运行环境(通过default指定选择多个子元素节点environment中的一个,以environment的id作为关联)。

<!--配置 mybatis 环境-->
<environments default="development">
    <!--id:数据源的名称-->
    <environment id="development">
        <!--事务类型:使用 JDBC 事务,使用 Connection 的提交和回滚-->
        <transactionManager type="JDBC"></transactionManager>
        <!--
                数据源 dataSource:创建数据库 Connection 对象
                type: POOLED 使用数据库的连接池
            -->
        <dataSource type="POOLED">
            <!--连接数据库的四大参数-->
            <property name="driver" value="com.mysql.cj.jdbc.Driver"/><!--注意数据库版本使用的是MySQL8,如果是mysql5的话,driver和url都不一样,参考学过的JDBC-->
            <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false&amp;serverTimezone=GMT"/>
            <property name="username" value="root"/>
            <property name="password" value="lijinghua"/>
        </dataSource>
    </environment>
</environments>

详情:点击查看官方文档https://mybatis.org/mybatis-3/zh/configuration.html#environments

建议对比着往下看!!!里面有详细的介绍,这里只说重点!!因为内容真的挺多…

  • 子元素节点 environment

    具体的一套环境,通过设置id进行区别,id保证唯一

    • 子元素节点 transactionManager(事务管理器 )

      Mybatis 框架是对 JDBC 的封装,所以 Mybatis 框架的事务控制方式,本身也是用 JDBC 的 Connection对象的 commit()、rollback() 、Connection 对象的 setAutoCommit()方法来设置事务提交方式的(自动提交和手动提交,默认手动提交!)。

      <!-- 语法 -->
      <transactionManager type="[ JDBC | MANAGED ]"/>
      
      <!--重载方法:获取数据库连接时设置成自动提交-->
      sqlSessionFactory.openSession();//默认手动提交
      sqlSessionFactory.openSession(true);//自动提交
      

      在这里插入图片描述

    • 子元素节点 dataSource(数据源)

      Mybatis 中访问数据库支持连接池技术,而且是采用的自己的连接池技术。在 Mybatis 的 mybatis.xml配置文件中,通过来实现 Mybatis 中连接池的配置。MyBatis 在初始化时,根据的 type 属性来创建相应类型的的数据源 dataSource。dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。数据源是必须配置的
      有三种内建的数据源类型:

      type="[UNPOOLED|POOLED|JNDI]")
      
      • unpooled: 这个数据源的实现只是每次被请求时打开和关闭连接。
      • pooled: 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来 , 这是一种使得
        并发 Web 应用快速响应请求的流行处理方式。
      • jndi:这个数据源的实现是为了能在如 Spring 或应用服务器这类容器中使用,容器可以
        集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。
      • 数据源也有很多第三方的实现,比如dbcp,c3p0,druid等等…

      前两个unpooled、pooled都实现了javax.sql.DataSource接口


4.3 mappers元素

在这里插入图片描述

    mappers,即映射器,用于注册我们的映射SQL语句文件。

    既然 MyBatis 的行为其他元素已经配置完了,我们现在就要定义 SQL 映射语句了。但是首先我们需要告诉 MyBatis 到哪里去找到这些语句。

    Java 在自动查找这方面没有提供一个很好的方法,所以最佳的方式是告诉 MyBatis 到哪里去找映射文件。你可以使用相对于类路径的资源引用、或完全限定资源定位符(包括 file:/// 的 URL)、或类名和包名等。映射器是MyBatis中最核心的组件之一,在MyBatis 3之前,只支持xml映射器,即:所有的SQL语句都必须在xml文件中配置。而从MyBatis 3开始,还支持接口映射器,这种映射器方式允许以Java代码的方式注解定义SQL语句,非常简洁。

4.3.1 引入资源的方式

​ 下面列举四种注册我们的映射文件方式。

4.3.1.1 使用相对于类路径的资源引用

    使用相对于类路径的资源,从 classpath 路径查找文件。

<mappers>
	<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
4.3.1.2 使用完全限定资源定位符URL
<mappers>
	<mapper url="file:///var/mappers/AuthorMapper.xml"/>
</mappers>
4.3.1.3 使用映射器接口实现类的完全限定类名

​ 使用前提:需要配置文件名称和接口名称一致,并且位于同一目录下。

注意:这里的同一目录指的是目录一致,至于你都放在src中的同一个包,还是把配置文件放在resources中且和接口目录一致即可。这里可以看看第二大节开始的目录预览和2.7小节就知道了。

<mappers>
	<mapper class="org.mybatis.builder.AuthorMapper"/>
</mappers>
4.3.1.4 将包内的映射器接口实现全部注册为映射器

    使用前提:需要配置文件名称和接口名称一致,并且位于同一目录下 。规则和上述一样。

<mappers>
	<package name="org.mybatis.builder"/>
</mappers>



4.4 mapper映射文件

    MyBatis 的真正强大在于它的映射语句,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 为聚焦于 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.lijinghua.dao.TeamDao">

	<!-- 省略以下sql语句:insert、delete、update、select-->
</mapper>

​ namespace中文意思:命名空间,作用如下:

  • namespace和子元素的id联合保证唯一 , 区别不同的mapper

  • 绑定DAO接口

    • namespace的命名必须跟某个接口同名
    • 接口中的方法与映射文件中sql语句id应该一一对应
  • namespace命名规则

    包名+类名



4.5 Properties优化

    数据库这些属性都是可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素来传递,优化我们的配置文件。

参看中文官方文档的properties这节:https://mybatis.org/mybatis-3/zh/configuration.html#properties

  1. 在资源目录下新建一个db.properties

    driver=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://127.0.0.1:3306/mybatis?
    useSSL=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
    username=root
    password=lijinghua
    
  2. 将文件导入properties 配置文件

    <?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>
        <!--导入properties文件,记得配置顺序-->
        <properties resource="db.properties"/>
        <!--配置 mybatis 环境-->
        <environments default="development">
            <!--id:数据源的名称-->
            <environment id="development">
                <!--事务类型:使用 JDBC 事务,使用 Connection 的提交和回滚-->
                <transactionManager type="JDBC"></transactionManager>
                <!--
                    数据源 dataSource:创建数据库 Connection 对象
                    type: POOLED 使用数据库的连接池
                -->
                <dataSource type="POOLED">
                    <!--连接数据库的四大参数-->
    <!--                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>&lt;!&ndash;注意数据库版本使用的是MySQL8,如果是mysql5的话,driver和url都不一样,参考学过的JDBC&ndash;&gt;-->
    <!--                <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false&amp;serverTimezone=GMT"/>-->
    <!--                <property name="username" value="root"/>-->
    <!--                <property name="password" value="lijinghua"/>-->
                    <property name="driver" value="${driver}"/>
                    <property name="url" value="${url}"/>
                    <property name="username" value="${username}"/>
                    <property name="password" value="${password}"/>
                </dataSource>
            </environment>
        </environments>
    
        <!--注册映射文件-->
        <mappers>
            <mapper resource="com/lijinghua/dao/TeamMapper.xml"/>
        </mappers>
    </configuration>
    



4.6 typeAliases优化

    类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。

常见的、内置的类型别名可以查看:https://mybatis.org/mybatis-3/zh/configuration.html#typeAliases

注意:这个别名用在resultType或者paramterType,不能用在namespace。

4.6.1 配置具体的类
<!--配置别名,注意顺序-->
<typeAliases>
	<typeAlias type="com.lijinghua.pojo.Team" alias="Team"/><!--当这样配置时, Team可以用在任何使用 com.lijinghua.pojo.Team 的地方-->
</typeAliases>
4.6.2 配置包目录+@Alias注解

    也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

<typeAliases>
		<package name="com.lijinghua.pojo"/>
</typeAliases>

    每一个在包 com.lijinghua.pojo 中的 Java Bean,若有注解,则别名为其注解值;在没有注解的情况下,会使用 Bean 的首字母小写或者大写都可以的非限定类名来作为它的别名

@Alias("myTeam")
public class Team {
    ...
}



4.7 其他的配置(了解)

4.7.1 设置settings

    可以实现:

  • 懒加载
  • 日志实现
  • 缓存开启关闭
  • ……

查看前面给出的官方文档以及https://mybatis.org/mybatis-3/zh/configuration.html#settings,里面给出了详细的实现步骤。

4.7.2 类型处理器

    无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。

你可以重写类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。

查看前面给出的官方文档以及https://mybatis.org/mybatis-3/zh/configuration.html#typeHandlers

4.7.3 对象工厂

    MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过有参构造方法来实例化。如果想覆盖对象工厂的默认行为,则可以通过创建自己的对象工厂来实现。

查看前面给出的官方文档以及https://mybatis.org/mybatis-3/zh/configuration.html#objectFactory



5. Mybatis对象分析

5.1 Mybatis的重要对象

在这里插入图片描述

  • Resources

    ​ Resources 类,顾名思义就是资源,用于读取资源文件。其有很多方法通过加载并解析资源文件,返回不同类型的 IO 流对象。

  • SqlSessionFactoryBuilder

    用于创建 SqlSessionFactory, 需要使用 SqlSessionFactoryBuilder 对象的 build() 方法 。 事实上使用SqlSessionFactoryBuilder的原因是将sqlSessionFactory这个复杂对象的创建交由Builder来执行,也就是使用了建造者设计模式。

    ​ 查看源码可以发现这个build()方法有多个重载版本,最终都会执行下述的这个build方法:

    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            var5 = this.build(parser.parse());//调用下面的build方法去创建SqlSessionFactory
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();
    
            try {
                inputStream.close();
            } catch (IOException var13) {
            }
    
        }
    
        return var5;
    }
    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }
    

    建造者模式:

    ​ 又称生成器模式,是一种对象的创建模式。可以将一个产品的内部表象与产品的生成过程分割开来, 从而可以使一个建造过程生成具有不同的内部表象的产品(将一个复杂对象的构建与它的表示分离, 使得同样的构建过程可以创建不同的表示)。这样用户只需指定需要建造的类型就可以得到具体产品,而不需要了解具体的建造过程和细节。

    ​ 在建造者模式中,角色分指导者(Director)与建造者(Builder): 用户联系指导者, 指导者指挥建造者, 最后得到产品。建造者模式可以强制实行一种分步骤进行的建造过程。

  • SqlSessionFactory

    用于创建SqlSession。SqlSessionFactory 接口对象是一个重量级对象(系统开销大的对象),是线程安全的,所以一个应用只需要一个该对象即可。创建SqlSession需要使用 SqlSessionFactory 接口的的 openSession()方法。

默认的 openSession()方法,它会创建有如下特性的 SqlSession:
1、会开启一个事务(也就是不自动提交)。
2、将从由当前环境配置的 DataSource 实例中获取 Connection 对象。事务隔离级别将会使用驱动或数据源的默认设置。
3、预处理语句不会被复用,也不会批量处理更新。

openSession(true):创建一个有自动提交功能的 SqlSession
openSession(false):创建一个非自动提交功能的 SqlSession,需手动提交,等同于openSession()

  • SqlSession

    ​ SqlSession 接口对象用于执行持久化操作。一个 SqlSession 对应着一次数据库会话,一次会话以SqlSession 对象的创建开始,以SqlSession 对象的关闭结束。

    • SqlSession 接口对象是线程不安全的,所以每次数据库会话结束前,需要马上调用其 close()方法,将其关闭。再次需要会话,再次创建。

      SqlSession 在方法内部创建,使用完毕后关闭。

    • SqlSession 类中有超过 20 个方法,我们常用的几乎都是执行语法相关的方法。

      这些方法被用来执行定义在 SQL 映射的 XML 文件中的 SELECT、INSERT、UPDATE 和 DELETE 语句。它们都会自行解释,每一句都使用语句的 ID 属性和参数对象,参数可以是原生类型(自动装箱或包装类)、JavaBean、POJO 或 Map。



5.2 Mybatis的运行流程与作用域

在这里插入图片描述

  1. 读取Mybatis的核心配置文件;
  2. SqlSessionFactoryBuilder 的作用在于创建 SqlSessionFactory,创建成功后,SqlSessionFactoryBuilder 就失去了作用,所以它只能存在于创建 SqlSessionFactory 的方法中,而不要让其长期存在。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
  3. SqlSessionFactory 可以被认为是一个数据库连接池,它的作用是创建 SqlSession 接口对象。因为MyBatis 的本质就是 Java 对数据库的操作,所以 SqlSessionFactory 的生命周期存在于整个MyBatis 的应用之中,所以一旦创建了 SqlSessionFactory,就要长期保存它,直至不再使用MyBatis 应用,所以可以认为 SqlSessionFactory 的生命周期就等同于 MyBatis 的应用周期。由于 SqlSessionFactory 是一个对数据库的连接池,所以它占据着数据库的连接资源。如果创建多个 SqlSessionFactory,那么就存在多个数据库连接池,这样不利于对数据库资源的控制,也会导致数据库连接资源被消耗光,出现系统宕机等情况,所以尽量避免发生这样的情况。因此在一般的应用中我们往往希望 SqlSessionFactory 作为一个单例,让它在应用中被共享。所以说SqlSessionFactory 的最佳作用域是应用作用域
  4. 如果说 SqlSessionFactory 相当于数据库连接池,那么 SqlSession 就相当于一个数据库连接(Connection 对象),你可以在一个事务里面执行多条 SQL,然后通过它的 commit、rollback等方法,提交或者回滚事务。所以它应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,把它归还给 SqlSessionFactory,否则数据库资源就很快被耗费精光,系统就会瘫痪,可以用 try-catch-finally 语句来保证其正确关闭。所以 SqlSession 的最佳的作用域是请求或方法作用域
  5. Mybatis底层自定义了Executor执行器的接口操作数据库,Executor接口有两个实现,一个基本的执行器,一个是缓存的执行器。
  6. Mapped statement 也是mybatis框架一个底层的封装对象,他包装了mybatis配置信息以及sql映射信息。xxxMapper.xml文件(映射文件)中的一个SQL语句对应一个Mapped statement对象,sql的id就是Mapped statement的id
  7. Mapped statement对SQL执行输入参数的定义,输入参数包括HashMap、基本类型、pojo,Executor通过Mapped statement在执行SQL语句前将输入java对象映射到sql语句中,执行完毕SQL之后,输出映射就是JDBC编码中的对preparedStatement 执行结果的定义。

内部源码解析:Mybatis的运行流程Mybatis解析和基本运行原理



6. Mybatis基于注解开发

6.1 面向接口编程

    java是一门面向对象语言,我们肯定知道什么是面向对象编程OOP,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程。面向接口编程和面向对象编程并不是平级的,它并不是比面向对象编程更先进的一种独立的编程思想,而是附属于面向对象思想体系,属于其一部分。或者说,它是面向对象编程体系中的思想精髓之一。

    根本原因: 解耦 , 可拓展 , 提高复用 , 分层开发中 , 上层不用管具体的实现 , 大家都遵守共同的标准, 使得开发变得容易 , 规范性更好。

    每个层次不是直接向其上层提供服务(即不是直接实例化在上层中),而是通过定义一组接口,仅向上层暴露其接口功能,上层对于下层仅仅是接口依赖,而不依赖具体类。这样做的好处是显而易见的,首先对系统灵活性大有好处。当下层需要改变时,只要接口及接口功能不变,则上层不用做任何修改。甚至可以在不改动上层代码时将下层整个替换掉。

注:

  • 面向接口编程中的“接口”:一种思想层面的用于实现多态性、提高软件灵活性和可维护性的架构部件;
  • 具体语言中的“接口”:将这种思想中的部件具体实施到代码里的手段。

6.2 基于注解开发

    Mybatis最初配置信息是基于 XML的,映射语句(SQL)也是定义在 XML 中的。而到MyBatis 3提供了新的基于注解的配置。不幸的是,Java 注解的的表达力和灵活性十分有限。最强大的 MyBatis 映射并不能用注解来构建。

个人理解:复杂的sql语句还是定义在XML中比较方便,简单的我们可以使用注解。

6.2.1 注解下sql类型
  • @select ()
  • @update ()
  • @Insert ()
  • @delete ()
6.2.2 基于注解开发的实现步骤
  1. 在接口中添加注解

    //查询全部球队
    @Select("select * from team")
    public List<Team> queryAll();
    //根据id查询球队
    @Select("select * from team where teamId = #{id}")
    User queryTeamById(@Param("id") int id);
    //添加一个球队
    @Insert("insert into team (teamName,location,createTime) values (#{teamName},#{location},#{createTime})")
    int addTeam(Team team);
    //修改一个球队
    @Update("update team set teamName=#{teamName},location=#{location}, where teamId = #{teamId}")
    int updateTeam(Team team);
    //根据id删除球队
    @Delete("delete from team where teamId = #{id}")
    int deleteTeam(@Param("id")int id);
    
  2. 在Mybatis的核心配置文件中注册映射文件

    <!--注册映射文件:绑定接口-->
    <mappers>
    	<mapper class="com.lijinghua.dao.TeamMapper"/>
    </mappers>
    

本质上还是利用了jvm的动态代理机制+反射。




7. Mybatis的关系映射

    数据库中存在一对一、多对一、一对多、多对多的映射关系,例如一个球队中存在多名球员,多名球员隶属于同一只球队。一对一已经在前面提到了,这里开始说一下多对一和一对多关系。

在这里插入图片描述

7.1 准备-创建Player实体类和对应接口

    PlayerMapper映射文件的注册或者是起别名已省略,可参照前面去写。

/**
 * 多方:球员的实体类
 */
@Data
public class Player {
    private Integer playerId;
    private String playerName;
    private Integer playerNum;
    private Integer teamId;
    //关系字段:多个球员隶属一只球队
    //多方持有一方的对象
    private Team team;

}
//接口
public interface PlayerMapper {
    //PlayerMapper接口中增加方法
	//public abstract Player queryById(int playerId);
    public abstract Player queryById1(int playerId);
    public abstract Player queryById2(int playerId);
    public abstract Player queryById3(int playerId);
}

7.2 多对一映射关系的处理

    多个球员隶属于一只球队,属于多对一映射关系。接下来修改PlayerMapper映射文件。

<!--手动设置球员信息的映射关系-->
<resultMap id="playerInfo" type="Player">
    <id column="playerId" property="playerId"/>
    <result column="playerName" property="playerName"/>
    <result column="playerNum" property="playerNum"/>
    <result column="teamId" property="teamId"/>
</resultMap>

7.2.1 连接查询—通过关联对象打点调用属性

    使用连接查询,并对结果集进行处理。

使用前提:多表的连接查询。

<select id="queryById1" resultMap="JoinTeamResult1">
    select * from player p inner join team t
    on p.teamId=t.teamId where playerId=#{id}
</select>
<!-- 方式1:通过关联对象打点调用属性的方式
    要求:连接查询
    如果连接查询,一般单独定义resultMap
    extends="表示继承的其他的resultMap的id"
    -->
<resultMap id="JoinTeamResult1" type="Player" extends="playerInfo">
    <id column="teamId" property="team.teamId"></id>
    <result column="teamName" property="team.teamName"></result>
    <result column="location" property="team.location"></result>
    <result column="teamName" property="team.teamName"></result>
    <result column="createTime" property="team.createTime"></result>
</resultMap>

7.2.2 连接查询—直接引用关联对象的Mapper映射

    使用连接查询+关联对象中已经存在被引用的resultMap,并对结果集进行处理。

    有没有感觉到新加的JoinTeamResult1的resultMap其实就相当于playerInfo+baseResultMap(之前我们在写球队时候的)??

    所以这里引入了第二种方法就是在写完playerInfo后,在JoinTeamResult1中直接调用已有的resultMap(baseResultMap),而不是自己写了,相当于第一种方法的简化版

使用前提:多表的连接查询+已经存在的resultMap

<select id="queryById2" resultMap="JoinTeamResult2">
    select * from player p inner join team t
    on p.teamId=t.teamId where playerId=#{id}
</select>
<!--方式2:直接引用关联对象的Mapper映射:要求连接查询
    property="关联对象的属性名"
    javaType="关联对象的类型"
    resultMap="关联对象的命名空间中的resultMap"
    -->
<resultMap id="JoinTeamResult2" type="Player" extends="playerInfo">
    <association property="team" javaType="Team"
                 resultMap="com.lijinghua.dao.TeamMapper.baseResultMap"></association>
</resultMap>

<!--TeamMapper中的resultMap-->
<resultMap id="baseResultMap" type="com.lijinghua.pojo.Team">
    <!--一般主键列用id,其余列用result
        column:表示数据库表中的列名,不区分大小写
        property:表示实体类中的对应的属性名,区分大小写
        javaType:实体类中的对应的属性的类型,可以省略,mybatis会自己推断
        jdbcType="数据库中的类型column的类型" 一般省略
        -->
    <id column="teamId" property="teamId" javaType="java.lang.Integer" ></id>
    <result column="teamName" property="teamName" javaType="java.lang.String"></result>
    <result column="location" property="location" javaType="java.lang.String"></result>
    <result column="createTime" property="createTime" javaType="java.util.Date"></result>
</resultMap>

7.2.3 嵌套查询—直接引用关联对象的单独查询

    使用嵌套查询,先查球员信息,再根据球员信息中的teamId去查询球队信息,最后返回处理结果集。

使用前提:嵌套查询+已存在的子查询方法

<select id="queryById3" resultMap="JoinTeamResult3">
    select * from player where playerId=#{id}
</select>
<!--方式3:直接引用关联对象的单独查询的方法:要求:关联对象的Maper中必须要求有单独的查询方法
    property="关联对象的属性名"
    javaType="关联对象的类型"
    select="关联对象的单独查询的语句"
    column="外键列"
    -->
<resultMap id="JoinTeamResult3" type="Player" extends="playerInfo">
    <association property="team" javaType="Team"
                 select="com.lijinghua.dao.TeamMapper.queryTeamById" column="teamId"></association>
</resultMap>

7.3 一对多映射关系的处理

    站在球队的角度上去看,一只球队中包含多名球员,属于一对多的映射关系。

    修改Team实体类,并在接口、映射文件中添加方法:

public class Team {
    private Integer teamId;
    private String teamName;
    private String location;
    private Date createTime;

    //关系字段:一只球队拥有多名球员
    //一方持有多方的集合
    private List<Player> players;
    
    //constructor+getter/setter
}
//TeamMapper接口中添加方法
public abstract Team queryTeamById1(int id);
public abstract Team queryTeamById2(int id);
//PlayerMapper接口中添加方法
public abstract List<Player> queryPlayersByTeamId(int teamId);
//PlayerMapper映射文件中添加方法
<select id="queryPlayersByTeamId" resultType="Player">
	select * from player where teamId=#{id}
</select>

​ 接下来修改PlayerMapper映射文件。


7.3.1 连接查询—引用关联对象的结果映射

    使用连接查询+关联对象中已经存在被引用的resultMap,并对结果集进行处理。

使用前提:多表的连接查询+已经存在的resultMap(也可以在collection中一个个自定义映射)

<select id="queryTeamById1" resultMap="joinResult1">
    select *
    from team t
    join player p
    on t.teamId = p.teamId
    where t.teamId = #{id};
</select>
<!--方式1:
    对多的连接查询:对多使用collection
    property="关联对象的集合名称"
    javaType="关联对象的集合类型"
    ofType="关联对象(集合)的泛型"
    resultMap="引用关联对象的结果映射"
    -->
<resultMap id="joinResult1" type="Team" extends="baseResultMap">
    <collection property="players" javaType="java.util.ArrayList" ofType="Player"
                resultMap="com.lijinghua.dao.PlayerMapper.playerInfo"></collection>
</resultMap>

7.3.2 嵌套查询—引用关联对象的单独查询

    使用嵌套查询,先查球队信息,再根据球队信息中的teamId去查询球员信息,最后返回处理结果集。

使用前提:嵌套查询+已存在的子查询方法

<select id="queryTeamById2" resultMap="joinResult2">
    select *
    from team
    where teamId = #{id};
</select>

<!--方式2:
    对多的连接查询:对多使用collection
    property="关联对象的集合名称"
    javaType="关联对象的集合类型"
    ofType="关联对象的集合的泛型"
    select="引用关联对象的单独查询的方法":使用的前提是关联对象中该方法可用
    column="引用关联对象的单独查询的方法的参数,一般是外键"
    -->
<resultMap id="joinResult2" type="Team" extends="baseResultMap">
    <collection property="players" javaType="java.util.ArrayList"
                select="com.lijinghua.dao.PlayerMapper.queryPlayersByTeamId" column="teamId"></collection>
</resultMap>

7.4 小结

  • 关联-association,用于一对一和多对一
  • 集合-collection,用于一对多
  • JavaType和ofType都是用来指定对象类型的
    • JavaType是用来指定pojo中属性的类型
    • ofType指定的是映射到集合属性中pojo的类型
  • 注意说明:
    • 保证SQL的可读性,尽量通俗易懂
    • 根据实际要求,尽量编写性能更高的SQL语句(保证实现后进行SQL优化)
    • 注意属性名和字段不一致的问题
    • 注意一对多和多对一中字段和属性对应的问题
    • 尽量使用Log4j,通过日志来查看自己的错误



8. 动态SQL

8.1官网描述

    MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。
    虽然在以前使用动态 SQL 并非一件易事,但正是 MyBatis 提供了可以被用在任意 SQL 映射语句中的强大的动态 SQL 语言得以改进这种情形。动态 SQL 元素和 JSTL 或基于类似 XML 的文本处理器相似。在 MyBatis 之前的版本中,有很多元素需要花时间了解。MyBatis 3 大大精简了元素种类,现在只需学习原来一半的元素便可。MyBatis 采用功能强大的基于 OGNL 的表达式来淘汰其它大部分元素。

中文官方网站有关动态SQL的描述:https://mybatis.org/mybatis-3/zh/dynamic-sql.html


8.2 原来的自定义实现动态SQL

/*原有的多条件分析:都是通过java中的字符串拼接实现
String sql="select * from team where 1 = 1 ";
// 如果用户输入了名称,就模糊查询
and teamName like '%?%'
// 如果用户输入了日期,按照日期区间查询
and createTime> ? and createTime< ?
//如果输入了地区,按照地区查询
and location =?";*/
if(vo.getName()!=null && !"".equals(vo.getName().trim())){
	sql+=" and teamName like '%"+vo.getName().trim()+"%'";
} 
if(vo.getBeginTime()!=null ){
	sql+=" and getEndTime>"+vo.getBeginTime();
} 
if(vo.getBeginTime()!=null ){
	sql+=" and createTime<="+vo.getEndTime();
} 
if(vo.getLocation()!=null && !"".equals(vo.getLocation().trim())){
	sql+=" and location ="+vo.getLocation().trim();
}

8.3 动态SQL之if标签

    需求:根据作者名字和博客名字来查询博客!如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询。

  1. 编写接口方法

    List<Blog> queryBlogIf(Map map);
    

    自行提前准备好博客Blog实体类、接口类的创建、映射文件的创建与注册,已经省略。

  2. 编写SQL语句

    <!--
        根据作者名字和博客名字来查询博客!
        如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询
        select * from blog where title = #{title} and author = #{author}
    -->
    <select id="queryBlogIf" parameterType="map" resultType="blog">
    	select * from blog where
        <if test="title != null">
            title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </select>
    
  3. 测试代码

    @Test
    public void testQueryBlogIf(){
        SqlSession session = MybatisUtils.getSession();
        BlogMapper mapper = session.getMapper(BlogMapper.class);
        HashMap<String, String> map = new HashMap<String, String>();
        map.put("title","Mybatis如此简单");
        map.put("author","lijinghua");
        List<Blog> blogs = mapper.queryBlogIf(map);
        System.out.println(blogs);
        session.close();
    }
    

     如果 author 等于 null,那么查询语句为 select * from user where title=#{title}。

    但是如果title为空呢?那么查询语句为 select * from user where and author=#{author},这是错误的SQL 语句,如何解决呢?请看下面的 where 语句!


8.4 动态SQL之where标签

    以上述例子为基础,修改上面的SQL语句:

<select id="queryBlogIf" parameterType="map" resultType="blog">
    select * from blog
    <where>
        <if test="title != null">
        	title = #{title}
        </if>
        <if test="author != null">
        	and author = #{author}
        </if>
    </where>
</select>

注意:

  • 这个“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉
  • 如果要使用模糊查询,可以用concat函数:title = like concat(concat(’%’,#{name}),’%’) ,或者使用美元符:title = ‘%${name}%’ ,后者容易出现SQL注入问题。

8.5 动态SQL之set标签

    如果在进行更新操作的时候,含有 set 关键词,我们怎么处理呢?

  1. 编写接口方法

    int updateBlog(Map map);
    

    自行提前准备好博客Blog实体类、接口类的创建、映射文件的创建与注册,已经省略。

  2. 编写SQL语句

    <!--注意set是用的逗号隔开-->
    <update id="updateBlog" parameterType="map">
        update blog
        <set>
        	<if test="title != null">
        		title = #{title},
        	</if>
        	<if test="author != null">
        		author = #{author}
        	</if>
        </set>
        where id = #{id};
    </update>
    
  3. 测试代码

    @Test
    public void testUpdateBlog(){
        SqlSession session = MybatisUtils.getSession();
        BlogMapper mapper = session.getMapper(BlogMapper.class);
        HashMap<String, String> map = new HashMap<String, String>();
        map.put("title","动态SQL");
        map.put("author","lijinghua");
        map.put("id","9d6a763f5e1347cebda43e2a32687a77");
        mapper.updateBlog(map);
        session.close();
    }
    

    动态SQL只更新非空属性列;而我们直接使用常规SQL更新的话,虽然可以更新成功,但是会把空属性列给一起更新。


8.6 动态SQL之choose标签

    有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句。

  1. 编写接口方法

    List<Blog> queryBlogChoose(Map map);
    

    自行提前准备好博客Blog实体类、接口类的创建、映射文件的创建与注册,已经省略。

  2. 编写SQL语句

    <select id="queryBlogChoose" parameterType="map" resultType="blog">
        select * from blog
        <where>
            <choose>
                <when test="title != null">
                	title = #{title}
                </when>
                <when test="author != null">
                	and author = #{author}
                </when>
                <otherwise>
                	and views = #{views}
                </otherwise>
            </choose>
        </where>
    </select>
    
  3. 测试代码

    @Test
    public void testQueryBlogChoose(){
        SqlSession session = MybatisUtils.getSession();
        BlogMapper mapper = session.getMapper(BlogMapper.class);
        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put("title","Java如此简单");
        map.put("author","lijinghua");
        map.put("views",9999);
        List<Blog> blogs = mapper.queryBlogChoose(map);
        System.out.println(blogs);
        session.close();
    }
    

8.7 动态SQL之foreach标签

    foreach标签可以帮助我们编写可以批量处理的SQL语句,例如查询 blog 表中 id 分别为1、2、3的lijinghua的博客信息。

  1. 编写接口方法

    List<Blog> queryBlogForeach(Map map);
    

    自行提前准备好博客Blog实体类、接口类的创建、映射文件的创建与注册,已经省略。

  2. 编写SQL语句

    <select id="queryBlogForeach" parameterType="map" resultType="blog">
        select * from blog
        <where>
            <!--
            collection:指定输入对象中的集合属性
            item:每次遍历生成的对象
            open:开始遍历时的拼接字符串
            close:结束时拼接的字符串
            separator:遍历对象之间需要拼接的字符串
            select * from blog where author="lijinghua" and (id=1 or id=2 or id=3)
            -->
            <if test="author != null">
            	author = #{author}
            </if>
            <foreach collection="ids" item="id" open="and (" close=")" separator="or">
            	id=#{id}
            </foreach>
        </where>
    </select>
    
  3. 测试代码

    @Test
    public void testQueryBlogForeach(){
        SqlSession session = MybatisUtils.getSession();
        BlogMapper mapper = session.getMapper(BlogMapper.class);
        HashMap 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.queryBlogForeach(map);
        System.out.println(blogs);
        session.close();
    }
    

8.8 动态SQL之SQL标签-重用SQL片段

    有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。

  1. 抽取共性的SQL片段

    <sql id="if-title-author">
        <if test="title != null">
        	title = #{title}
        </if>
        <if test="author != null">
        	and author = #{author}
        </if>
    </sql>
    
  2. 编写SQL语句,并引用SQL片段

    <select id="queryBlogIf" parameterType="map" resultType="blog">
        select * from blog
        <where>
            <!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace-->
            <include refid="if-title-author"></include>
            <!-- 在这里还可以引用其他的 sql 片段 -->
        </where>
    </select>
    

    注意:

    1. 最好基于单表来定义 sql 片段,提高片段的可重用性;
    2. 在 sql 片段中不要包括 where。



9. 分页查询

    在学习mybatis等持久层框架的时候,会经常对数据进行增删改查操作,使用最多的是对数据库进行查询操作,如果查询大量数据的时候,我们往往使用分页进行查询,也就是每次处理小部分数据,这样对数据库压力就在可控范围内。


9.1 limit实现分页

#语法
SELECT * FROM table LIMIT stratIndex,pageSize
SELECT * FROM table LIMIT 5,10; // 检索记录行 6-15
#为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为 -1:
SELECT * FROM table LIMIT 95,-1; // 检索记录行 96-last.
#如果只给定一个参数,它表示返回最大的记录行数目:
SELECT * FROM table LIMIT 5; //检索前 5 个记录行
#换句话说,LIMIT n 等价于 LIMIT 0,n。

limit分页语法

​ 实现步骤:

  1. 添加接口方法

    //选择全部用户实现分页
    List<User> selectUser(Map<String,Integer> map);
    
  2. 添加SQL语句

    <select id="selectUser" parameterType="map" resultType="user">
        select * from user limit #{startIndex},#{pageSize}
    </select>
    
  3. 测试代码

    //分页查询 , 两个参数startIndex , pageSize
    @Test
    public void testSelectUser() {
        SqlSession session = MybatisUtils.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        int currentPage = 1; //第几页
        int pageSize = 2; //每页显示几个
        Map<String,Integer> map = new HashMap<String,Integer>();
        map.put("startIndex",(currentPage-1)*pageSize);
        map.put("pageSize",pageSize);
        List<User> users = mapper.selectUser(map);
        for (User user: users){
        	System.out.println(user);
        } 
        session.close();
    }
    

9.2 RowBounds分页(了解)

    我们除了使用Limit在SQL层面实现分页,也可以使用RowBounds在Java代码层面实现分页,当然此种方式作为了解即可。我们来看下如何实现的!

​ 实现步骤:

  1. 添加接口方法

    //选择全部用户RowBounds实现分页
    List<User> getUserByRowBounds();
    
  2. 添加SQL语句

    <select id="getUserByRowBounds" resultType="user">
    	select * from user
    </select>
    
  3. 测试代码

    @Test
    public void testUserByRowBounds() {
        SqlSession session = MybatisUtils.getSession();
        int currentPage = 2; //第几页
        int pageSize = 2; //每页显示几个
        RowBounds rowBounds = new RowBounds((currentPage-1)*pageSize,pageSize);
        //通过session.**方法进行传递rowBounds,[此种方式现在已经不推荐使用了]
        List<User> users = session.selectList("com.lijinghua.mapper.UserMapper.getUserByRowBounds", null, rowBounds);
        for (User user: users){
        	System.out.println(user);
    	}
        session.close();
    }
    

9.3 PageHelper插件实现分页

9.3.1 在POM文件中添加PageHelper依赖
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.10</version>
</dependency>
9.3.2 Mybatis核心配置文件中添加插件配置
<!-- 引入 pageHelper插件 注意配置顺序 -->
<!--注意这里要写成PageInterceptor, 5.0之前的版本都是写PageHelper, 5.0之后要换成PageInterceptor-->
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <!--reasonable:分页合理化参数,默认值为false,直接根据参数进行查询。
        当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。
        方言可以省略,会根据连接数据的参数url自动推断-->
    	<property name="reasonable" value="true"/>
    </plugin>
</plugins>
9.3.3 PageHelper的使用
@Test
public void test5() {
    // PageHelper.startPage 必须紧邻查询语句,而且只对第一条查询语句生效;不写会查出全部。使用了拦截器
    PageHelper.startPage(2,5);//未来在service层
    List<Team> teams = teamMapper.queryAll();//查询的SQL语句结尾不能有分号
    teams.forEach(team-> System.out.println(team));
    PageInfo<Team> info=new PageInfo<>(teams);//PageHelper帮我们封装好了的分页实体类,具体代码已省略
    System.out.println("分页信息如下:");
    System.out.println("当前页:"+info.getPageNum());
    System.out.println("总页数:"+info.getPages());
    System.out.println("前一页:"+info.getPrePage());
    System.out.println("后一页:"+info.getNextPage());
    System.out.println("navigatepageNums:"+info.getNavigatepageNums());
    for (int num : info.getNavigatepageNums()) {
    	System.out.println(num);//导航页码
    }
}



10. Mybatis缓存

大佬更详细的缓存讲解:https://blog.csdn.net/weixin_32762235/article/details/112591639

    缓存是一般的ORM 框架都会提供的功能,目的就是提升查询的效率和减少数据库的压力。将经常查询的数据存在缓存(内存)中,用户查询该数据的时候不需要从磁盘(关系型数据库文件)上查询,而是直接从缓存中查询,提高查询效率,解决高并发问题

    MyBatis 也有一级缓存和二级缓存,并且预留了集成第三方缓存的接口。

在这里插入图片描述


10.1 一级缓存

10.1.1 一级缓存概念

    一级缓存也叫本地缓存(看上面贴的文章DefaultSqlSession默认使用的实现类PerpetualCache对象叫 localCache),是SqlSession级别的缓存。

    在操作数据库时需要构造 sqlSession对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
    一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了
    Mybatis默认开启一级缓存,存在内存中(本地缓存)不能被关闭,可以调用clearCache()来清空本地缓存,或者改变缓存的作用域。

一级缓存就是个HashMap。

10.1.2 一级缓存分析与使用

在这里插入图片描述

  1. 当用户发起第一次查询team=1001的时候,先去缓存中查找是否有team=1001的对象;如果没有,继续向数据中发送查询语句,查询成功之后会将teamId=1001的结果存入缓存中;

  2. 当用户发起第2次查询team=1001的时候,先去缓存中查找是否有team=1001的对象,因为第一次查询成功之后已经存储到缓存中,此时可以直接从缓存中获取到该数据,意味着不需要再去向数据库发送查询语句。

  3. 如果SqlSession执行了commit(有增删改的操作),此时该SqlSession对应的缓存区域被整个清空,目的避免脏读

    前提:SqlSession未关闭

    实例:根据球队id查询球队信息:

@Test
public void queryTeamById() {
    SqlSession sqlSession = null;
    try {
        sqlSession = MybatisUtil.getSession();

        TeamDao mapper = sqlSession.getMapper(TeamDao.class);
        Team team1 = mapper.queryTeamById(1001);//第一次查询出来,存入一级缓存,SQL执行了一次
        Team team1 = mapper.queryTeamById(1001);//第二次直接从一级缓存中取出,SQL没有执行

        System.out.println(team1 == team2);//true,用的是同一个对象
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        sqlSession.close();
    }
}

在这里插入图片描述


10.1.3 一级缓存的清空方式
  1. session.clearCache( ) ;
  2. execute update(增删改) ;
  3. session.close( );
  4. xml配置 flushCache=“true” ;
  5. rollback;
  6. commit。

10.1.4 一级缓存常见的失效情况
  1. 使用了不同的sqlSession(一级缓存是sqlSession级别的);
  2. sqlSession相同,查询条件不同(例如第一次查询id=1001,第二次查询id=1002);
  3. sqlSession相同,但是两次查询之间执行了增删改操作(会清空缓存);
  4. sqlSession相同,但是手动清空了缓存(clearCache);
  5. ……

10.2 二级缓存

10.2.1 二级缓存概念

    二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存。他是基于namespace级别(Mapper级别)的缓存,一个名称空间,对应一个二级缓存。

    多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession去操作数据库得到数据会存在二级缓存区域,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的
    二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace。不同的sqlSession两次执行相同namespace下的sql语句参数相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。
    Mybatis默认没有开启二级缓存,需要在setting全局参数中配置开启二级缓存。如果缓存中有数据就不用从数据库中获取,大大提高系统性能。


10.2.2 二级缓存分析与使用

在这里插入图片描述

    使用二级缓存步骤:

  1. 在Mybatis核心配置文件中设置开启全局缓存

    <!--是否开启二级缓存,默认false-不开启, true:开启-->
    <setting name="cacheEnabled" value="true"/>
    
  2. 在每个需要使用二级缓存的mapper.xml中配置使用二级缓存(因为二级缓存是Mapper级别的),这个配置非常简单

    <!--添加cache标签即可开启这个Mapper的二级缓存(有默认配置),当然也可以对其进行配置,如下例-->
    
    <!--这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的
    512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。-->
    <cache
    	eviction="FIFO"
    	flushInterval="60000"
    	size="512"
    	readOnly="true"/>
    

    建议具体使用参考中文官方网站详细说明:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html#cache

    没有比它更标准的使用说明了。

  3. 实体类必须实现序列化接口

在这里插入图片描述

Mybatis为什么要求实体类必须实现序列化?

注意:

  1. 查出的数据都会被默认先放在一级缓存中,只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中。只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据。
  2. 如果你的MyBatis使用了二级缓存,并且你的Mapper和select语句也配置使用了二级缓存,那么在执行select查询的时候,MyBatis会先从二级缓存中取输入,其次才是一级缓存,最后没有就去查数据库。
  3. 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。
  4. sqlSession必须从同一个Factiory中获取
  5. select标签在启用二级缓存后,都会默认自动开启二级缓存,映射语句文件中的所有CUD操作(结合第3点去看)将会刷新缓存。

10.2.3 二级缓存的禁用

    对于变化比较频繁的SQL,可以禁用二级缓存。

    在开始了二级缓存的XML中对应的statement中设置useCache=false禁用当前Select语句的二级缓存,意味着该SQL语句每次只需都去查询数据库,不会查询缓存。useCache默认值是true。对于一些很重要的数据尽量不要放在二级缓存中。


10.2.4 二级缓存的使用场景
  1. 因为所有的增删改都会刷新二级缓存,导致二级缓存失效,所以适合在查询为主的应用中使用,比如历史交易、历史订单的查询。否则缓存就失去了意义。
  2. 如果多个namespace 中有针对于同一个表的操作,比如Blog 表,如果在一个namespace 中刷新了缓存,另一个namespace 中没有刷新,就会出现读到脏数据的情况。所以,推荐在一个Mapper 里面只操作单表的情况使用

10.3 第三方缓存-EhCache(了解)

http://mybatis.org/ehcache-cache/



11. 反向生成插件(了解)

    可以通过配置文件,对我们的接口、映射文件、实体类进行反向生成,减少了代码的编写量。

11.1 在POM文件中配置插件

<!--反向生成插件-->
<plugin>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-maven-plugin</artifactId>
    <version>1.3.5</version>
    <configuration>
    	<!--配置文件的路径-->
    	<configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
    	<overwrite>true</overwrite>
    </configuration>
    <dependencies>
    	<dependency>
    		<groupId>org.mybatis.generator</groupId>
    		<artifactId>mybatis-generator-core</artifactId>
    		<version>1.3.5</version>
    	</dependency>
    </dependencies>
</plugin>

11.2 编写配置文件generatorConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<!-- 配置生成器 -->
<generatorConfiguration>
    <!--可以把如下步骤中的配置进行统一管理,放到一个properties文件中-->
    <!--<properties resource="generator/generator.properties"/>-->
    
    
    <!--1、数据库驱动jar:添加自己的jar路径 -->
    <classPathEntry
            location="D:\repository\mysql\mysql-connector-java\8.0.23\mysql-connector-java-8.0.23.jar" />
    <context id="MyBatis" targetRuntime="MyBatis3">
        <!--去除注释 -->
        <commentGenerator>
            <property name="suppressAllComments" value="true" /><!-- 是否取消注释 -->
            <property name="suppressDate" value="true" /> <!-- 是否生成注释代时间戳-->
        </commentGenerator>
        <!--2、数据库连接 -->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&amp;characterEncoding=utf-
8&amp;useSSL=false&amp;serverTimezone=GMT"
                        userId="root"
                        password="lijinghua">
            <!--解决连接到别的数据库表问题-->
            <property name="nullCatalogMeansCurrent" value="true" />
            <!--解决不生成delete、update等-->
            <property name="useInformationSchema" value="true"/>
        </jdbcConnection>
        <!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer;
        为 true时把JDBC DECIMAL和NUMERIC类型解析为java.math.BigDecimal -->
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>
        <!--3、生成实体类 指定包名 以及生成的地址 (可以自定义地址,但是路径不存在不会自动创建
        使用Maven生成在target目录下,会自动创建) -->
        <javaModelGenerator targetPackage="com.lijinghua.pojo"
                            targetProject="src\main\java">
            <property name="trimStrings" value="true" />
        </javaModelGenerator>
        <!--4、生成SQLmapper.xml映射文件 -->
        <sqlMapGenerator targetPackage="com.lijinghua.mapper"
                         targetProject="src\main\resources">
        </sqlMapGenerator>
        <!--5、生成Dao(Mapper)接口文件,-->
        <javaClientGenerator type="XMLMAPPER"
                             targetPackage="com.lijinghua.mapper"
                             targetProject="src\main\java">
        </javaClientGenerator>
        <!--6、要生成哪些表(更改tableName和domainObjectName就可以) -->
        <!-- tableName:要生成的表名
        enableCountByExample:Count语句中加入where条件查询,默认为true开启
        enableUpdateByExample:Update语句中加入where条件查询,默认为true开启
        enableDeleteByExample:Delete语句中加入where条件查询,默认为true开启
        enableSelectByExample:Select多条语句中加入where条件查询,默认为true开启
        selectByExampleQueryId:Select单个对象语句中加入where条件查询,默认为true开启
        -->
        <table tableName="Team"
               enableCountByExample="false"
               enableUpdateByExample="false"
               enableUpdateByPrimaryKey="false"
               enableDeleteByExample="false"
               enableDeleteByPrimaryKey="false"
               enableSelectByExample="false"
               selectByExampleQueryId="false">
            <property name="useActualColumnNames" value="true"/><!--开启转换的命名规范-->
        </table>
    </context>
</generatorConfiguration>

额外的参考:https://blog.csdn.net/legendaryhaha/article/details/106433761


11.3 运行插件反向生成

在这里插入图片描述

注意只能运行一次(多次会重复追加),运行完毕显示BUILD SUCCESS即为成功。


11.4 使用反向生成中的多条件查询方法

    一般的方法大致使用上没什么与自己写的没什么大的区别,可以自己去尝试生成一个去看看源码。生成的xxx实体类一般伴随着一个xxxExample的类,这个可以理解成我们的一个为对应实体类提供多条件、排序等功能的服务类。

@Test
public void test2(){
    //为多条件排序查询服务
    TeamExample example=new TeamExample();//Team实体类对应的服务类
    //看做条件的容器
    TeamExample.Criteria criteria = example.createCriteria();//成员内部类
    //往容器中添加条件(这些方法都是反向自动生成了的,这个插件不考虑类型的影响(比如字符串类型的只有等值比较、模糊查,没有区间between查),全部给你生成出来了)
    criteria.andTeamNameLike("%人%");
    criteria.andTeamIdBetween(1001,1020);
    List<Team> teams = mapper.selectByExample(example);//mapper是我们根据反向生成的映射文件、接口去生成的代理对象 一样是动态代理 
    for (Team team : teams) {
    	System.out.println(team);//反向生成的实体类Team
    }
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值