Java核心-MyBatis

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用版 2核4GB 50GB
简介: Java核心-MyBatis

Mybatis

介绍

基本介绍

Mybatis是一个ORM框架。

ORM:Object relationship mapping,对象关系映射。什么叫对象关系映射呢?其实就是说Mybatis这个框架可以把关系型数据库表中的记录映射对象,可以把对象映射为关系型数据库表中记录。

基础入门

  • 第一步:导包:写入maven中的pom.xml
<!-- Mybatis-->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
</dependency>
<!-- 数据库驱动包-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>
<!-- junit-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>
  • 第二步:配置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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!-- 数据库连接的配置-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/practice?useSSL=false&amp;characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
        
    </environments>
    
    <!-- 需要把放置SQL语句的的配置文件配置到这里-->
    <mappers>
        <!--<mapper resource="org/mybatis/example/BlogMapper.xml"/>-->
        <mapper resource="AccountMapper.xml"/>
    </mappers> 
    
</configuration>
• 28
• 29
  • 第三步:配置Mapper配置文件,这个配置文件里面放置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">
<!-- Namespace:命名空间,在Mybatis指代这个文件,每一个放SQL语句的配置文件都有一个自己的命名空间  -->
<mapper namespace="cskaoyan">
  
    <!-- 每一个标签有自己的id值,id值不能重复 resultType是返回类型,配置对应类型的全限定名称 -->
    <select id="selectAccountById" resultType="com.cskaoyan.vo.Account">
        select * from account where id = #{id}
    </select>
    <!--<insert id=""-->
    <!--<delete id=""-->
    <!--<update id=""-->
</mapper>
  • 第四步:编写代码
// 获取SqlSessionFactory,需要先获取SqlSessionFactoryBuilder
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// 获取主配置文件输入流
// ClassLoader classLoader = MybatisMain.class.getClassLoader();
// InputStream stream = classLoader.getResourceAsStream("mybatis-config.xml");
InputStream stream = Resources.getResourceAsStream("mybatis-config.xml");
// 获取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = builder.build(stream);
// 获取SqlSession SqlSession可以帮助我们去执行SQL语句
SqlSession sqlSession = sqlSessionFactory.openSession();
// 可以根据SQLsession去执行对应的SQL语句
Account account = sqlSession.selectOne("cskaoyan.selectAccountById", 1);
// 打印Account
System.out.println(account);
// 关闭SQLsession
sqlSession.close();
  • 注意:Mybatis的SqlSession默认是不自动提交的,所以在执行完之后需要手动提交:sqlSession.commit()
  • 设置自动提交的方法
// 获取SqlSession SqlSession可以帮助我们去执行SQL语句
sqlSession = sqlSessionFactory.openSession();
// 表示获取到的SqlSession 是自动提交的
sqlSessionFactory.openSession(true);

动态代理

通过动态代理,可以不指定SQL语句的坐标就能执行对应的SQL语句

使用方式

  • 提供一个接口,全限定类名与SQL语句配置文件中的Namespace值一致
  • 接口中的方法名与配置文件中的id对应
  • 以上两点就完成了SQL语句的坐标的定位
  • 方法的返回值要和配置文件中的resultType对应
  • 如果方法的返回值是list或者数组,那么resultType中只需要填该数组中对应的对象的全限定类名
  • 参数的类型和名字必须要对应
  • 接口和其对应的Mapper配置文件建议同名,并且编译后在同一级目录下
  • 实现的方法:在resources包下建一个和源代码包中一样的包名
  • 在idea中建包的时候要注意不能直接使用com.fh.xxx的形式,应该使用com/fh/xxx
public interface AccountMapper {  //自定义的接口
 
    Account selectAccountById(Integer id);
}
<!-- Namespace:命名空间,在Mybatis指代这个文件,每一个放SQL语句的配置文件都有一个自己的命名空间  -->
<mapper namespace="com.cskaoyan.mapper.AccountMapper">
    <select id="selectAccountById" resultType="com.cskaoyan.vo.Account">
        select * from account where id = #{id}
    </select>
    <insert id="insertAccount">
        insert into account values (#{id},#{name},#{money},#{role})
    </insert>
    <!--<insert id=""-->
    <!--<delete id=""-->
    <!--<update id=""-->
</mapper>
public static void main(String[] args) throws IOException {
//        AccountMapper accountMapper = new AccountMapperImpl();
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        // 获取到的是Mybatis帮助我们生成的代理对象,即AccountMapper的实现类
        AccountMapper accountMapper = sqlSession.getMapper(AccountMapper.class);//传入接口的字节码文件
        Account account = accountMapper.selectAccountById(2);
        // sqlSession.selectOne("com.cskaoyan.mapper.AccountMapper.selectAccountById",2);
        System.out.println(account);
    }

设置

properties

可以读取外部的properties文件,方便对Mybatis的动态修改

<properties resource="jdbc.properties"/>
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>

settings

设置日志、缓存、懒加载等配置

<settings>
        <!-- 配置日志-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

类型别名

通过类型别名对我们自定义的类来起别名,使代码在书写的时候更方便,但同时也会降低可读性,所以建议合理的起别名

<typeAliases>
  <typeAlias type = "com.cskaoyan.vo.Account" alias="a"/>
</typeAliases>

一些常见的基本和包装类型还有集合类型有内置的别名,不区分大小写

别名 映射的类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator

环境配置

可以针对不同的环境来配置,基本没用

<environments default="development">
    <!-- id: 环境的名字-->
    <environment id="development">
        <!-- 事务管理器
        JDBC: 使用Connection来管理事务
        MANAGED:使用容器(Spring)来管理事务
        -->
        <transactionManager type="JDBC"/>
        <!-- 数据库连接池的配置
        POOLED:使用Mybatis给我们内置的数据库连接池
        UNPOOLED:不使用数据库连接池,每次都会新建连接
        JNDI:可以支持我们使用外部的数据源(DBCP、C3p0)
        -->
        <dataSource type="POOLED">
            <!-- 数据库连接的配置-->
            <property name="driver" value="${driver}"/>
            <property name="url" value="${url}"/>
            <property name="username" value="${username}"/>
            <property name="password" value="${password}"/>
        </dataSource>
    </environment>
    <!--<environment id="test">-->
        <!--<transactionManager type="JDBC"/>-->
        <!--<dataSource type="POOLED">-->
            <!--&lt;!&ndash; 数据库连接的配置&ndash;&gt;-->
            <!--<property name="driver" value="${driver}"/>-->
            <!--<property name="url" value="${url}"/>-->
            <!--<property name="username" value="${username}"/>-->
            <!--<property name="password" value="${password}"/>-->
        <!--</dataSource>-->
    <!--</environment>-->
    <!--<environment id="prod">-->
        <!--<transactionManager type="JDBC"/>-->
        <!--<dataSource type="POOLED">-->
            <!--&lt;!&ndash; 数据库连接的配置&ndash;&gt;-->
            <!--<property name="driver" value="${driver}"/>-->
            <!--<property name="url" value="${url}"/>-->
            <!--<property name="username" value="${username}"/>-->
            <!--<property name="password" value="${password}"/>-->
        <!--</dataSource>-->
    <!--</environment>-->
</environments>

映射器

配置各个Mapper.xml映射文件

<mappers>
  <mapper resource = "com/fh/mapper/UserMapper.xml"/> 直接使用相对路径来读取,只能读取单个文件
</mappers>
<mappers>
  <package name="com.fh.mapper"/> //推荐的用法,这样可以读取mapper包下的所有文件
</mappers>

输入映射

如何将接口中的参数传入配置文件中的预编译代码中

一个简单参数

简单参数 = 基本类型 + 包装类型 + String

mapper

Account selectAccountById(Integer id);

mapper.xml

<select id = "selectAccountById" resultType="xxxx">
select * from account where id = #{id}
</select>

传入多个参数

mapper

//    多个参数
    // 根据Id修改名字和角色
    int updateAccountNameAndRoleById(@Param("id")Integer id,
                                     @Param("name") String name,
                                     @Param("role") String role);

mapper.xml

<update id="updateAccountNameAndRoleById">
    update account set name = #{name},role=#{role}
    where id = #{id}
</update>

需要使用注解值来标明每个参数对应的名字,如果不使用注解就只能使用按顺序读取

传入对象

mapper

// 传入对象
int insertAccount(Account account);
// 传入对象 加注解
int insertAccountWithParam(@Param("account") Account account);

mapper.xml

<insert id="insertAccount">
    insert into account values (#{id},#{name},#{money},#{role})
</insert>
<insert id="insertAccountWithParam">
    insert into account values (#{account.id},#{account.name},#{account.money},#{account.role})
</insert>
  • 如果没有注解,直接使用#{成员变量名}来取值
  • 如果有注解,那么使用#{注解的名字.成员变量名}来取值

传入map

mapper

// 传入Map
// 规定:map里面只能传 name 和 role
List<Account> selectAccountListByMap(@Param("map") Map<String,Object> map);

mapper.xml

<select id="selectAccountListByMap" resultType="com.cskaoyan.vo.Account">
    select * from account where name = #{map.name} or role = #{map.role}
</select>
  • 当没有注解的时候,直接使用#{key}取值
  • 当有注解的时候,使用#{注解的值.key}来取值

按位置来传值

mapper

// 按位置来传值
List<Account> selectAccountListByNameOrRole(String name,String role);

mapper.xml

<select id="selectAccountListByNameOrRole" resultType="com.cskaoyan.vo.Account">
        select * from account where name = #{param1} or role = #{param2}
<!--        select * from account where name = #{arg0} or role = #{arg1} -->
    </select>
  • #{arg0}、#{arg1}、#{arg2}… 这种方式来取值,arg0代表参数列表第一个参数,arg1代表参数列表第二个参数
  • #{param1}、#{param2}、#{param3} … 这种方式来取值,param1代表第一个参数,param2代表第二个参数

${}和#{}的区别

#{} 取值的时候采用的是 PrepareStatement预编译占位的方式去设值,${}是采用 statement 字符串拼接的方式去设值,预编译占位的方式比没有数据库注入的问题,比较安全

${} 来取值这种取值方式有两个使用场景

  • 当我们传入表名或者是列名的时候需要使用${} 来取值
  • 传入列名
    例如当我们使用 group by 或者是order by关键字的时候,需要根据对应的列名来分组或者是排序的时候,这种业务场景下需要传入列名,这个时候就需要使用 ${} 来取值
    mapper
// 传入列名
List<Account> selectAccountOrderByColumn(@Param("columnName") String columnName);
  • mapper.xml
<select id="selectAccountOrderByColumn" resultType="com.cskaoyan.vo.Account">
    select * from account order by ${columnName} desc
</select>
  • order by、group by、 limit 等等后面跟的值都需要使用 ${} 来取值
  • 传入表名因为MySQL的单表是有性能极限的(500w),假如当对应的业务的数据超过500条的时候,假如有1000w条,这个时候就可以把这些数据分到不同的表中来存储。分表之后会带来一些问题,我们对于整体的排序,整体的分组其实是不太方便了业内有两个有名的数据库中间件,帮助我们去解决分库分表之后的一些问题
  • MyCat
  • Sharing-JDBC
  • mapper
// 传入表名
Account selectAccountByIdAndTableName(@Param("tableName") String tableName,
                                      @Param("id") Integer id);
  • mapper.xml
<select id="selectAccountByIdAndTableName" resultType="com.cskaoyan.vo.Account">
    select * from ${tableName} where id = #{id}
</select>

总之,如果预编译的语句不能确定整体的查询结果,即有可能会有歧义的时候,就需要使用${},而如果是在过滤的时候就可以使用#{}

即,如果语句中需要 = xxx的时候就写成 = #{xxx}

输出映射

Mybatis如何将值返回

简单类型

  • 单个值:直接返回即可,可以合理使用别名
    mapper
// 一个简单类型
String selectNameById(@Param("id") Integer id);
  • mapper.xml
<select id="selectNameById" resultType="string">
    select name from account where id = #{id}
</select>
  • 多个值:当返回值是一个集合或者数组的时候,resultType里面要写集合或者数组里面单个值的类型
    mapper
List<String> selectNameList();
  • mapper.xml
<select id="selectNameList" resultType="string">
    select name from account
</select>

对象

  • 单个对象:resultType里面需要填该对象的全限定类名
    mapper
Account selectAccountById(@Param("id") Integer id);
  • mapper.xml
<select id="selectAccountById" resultType="com.cskaoyan.vo.Account">
    select * from account where id = #{id}
</select>
  • 多个对象:与单个对象一样,resultType里面需要填该对象的全限定类名
    mapper
List<Account> selectAccountListByRole(String role);
  • mapper.xml
<select id="selectAccountListByRole" resultType="com.cskaoyan.vo.Account">
    select * from account where role = #{role}
</select>

ResultMap

将表中的列名和对象中的属性映射

  • 单个值
    mapper
AccountVO selectAccountVOById(@param("id") Integer id)
  • mapper.xml
<resultMap id="accountVOMap" type="com.cskaoyan.vo.AccountVO">
    <!-- id是指该映射map的id,可以调用,type是该map要映射的对象的全限定类名-->
    <!-- 主键的映射 -->
    <!--
  id:主键的映射
  result:普通列的映射
  column:查询结果的表的列名
  property:对应的返回结果的成员变量名
  -->
    <id column="id" property="uid"/>
    <!-- 其他普通列的映射-->
    <result column="name" property="username"/>
    <result column="money" property="money"/>
    <result column="role" property="role"/>
</resultMap>
<select id="selectAccountVOById" resultMap="accountVOMap">
    select id,name,money,role from account where id = #{id}
</select>
  • 一定要注意列名和对象中属性名的区别,我们在查询的时候一般使用列名,在返回的时候封装进对象才用对象中的属性名

插件

Lombok

可以自动生成类中的常用的方法,后期修改类中属性的时候效果显著

  • 导包
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
</dependency>
  • 在idea中安装插件
  • 使用
    @Getter @Setter @ToString @Data

Mybatis的插件

MybatisCodeHelperPro 直接在idea中下载

动态SQL

where

  • 自动拼接where关键字
    mapper.xml
<select id="selectStudentById" resultMap="studentMap">
    select * from student
    <where>
    id = #{id}
    </where>
</select>    
  • 可以帮助我们去除和where关键字相邻的and或者是or关键字
  • 当where标签中,没有条件符合的时候,不拼接where关键字

mapper

// 根据条件去查询对应的StudentList
// 传入一个student对象,
// 假如student对象的成员变量有值,那么我们就根据对应的成员变量去匹配
// 假如对应的成员变量没有值,那么我们就不看这个条件
List<Student> selectStudentListBySelective(@Param("student") Student student);

mapper.xml

<select id="selectStudentListBySelective" resultMap="studentMap">
    select * from student
    <where>
    <if test="student.id!=null">
          and id = #{student.id}
        </if>
        <if test="student.name!=null">
          and name = #{student.name}
        </if>
        <if test="student.clazz!=null">
          and class = #{student.clazz}
        </if>
        <if test="student.score!=null">
          and score = #{student.score}
        </if>
    </where>
</select>

if

根据条件来改变SQL语句

<select id = "selectStudentListByScore" resultMap="studentMap">
  select * from student where 
    <if test="score gt 60">
    score &gt; #{score}
    </if>
    <if test="score lte 60">
    score &lt;= #{score}
    </if>
</select>

if标签中使用的是OGNL表达式,这个表达式的类型必须是boolean类型,并且符号也需要使用特定的符号

在xml中写SQL语句时,大于号和小于号也需要使用转义字符

原字符 转义字符 OGNL表达式
> &gt; gt
< &lt; lt
>= &gt;= gte
<= &lt;= lte
!= != !=

choose when otherwise

相当于java中的if…else

mapper

// 假如传入的id大于10,那么我们就查询id比10大的学生
// 否则查询id小于等于10 的学生
List<Student> selectStudentListById(@Param("id") Integer id);

mapper.xml

<select id="selectStudentListById" resultMap="studentMap">
    select * from student
    <where>
    <choose>
        <when test="id gt 10">
          id &gt;10
        </when>
        <otherwise>
          id &lt;=10
        </otherwise>
     </choose>
    </where>
</select>   

trim

修剪,可以增加或者删除指定的字符

mapper

// 修改student对象里面值,根据Id去修改
// 假如student里面成员变量有值,我们就去修改
// 假如没有值,就不修改
int updateStudentByIdSelective(@Param("student") Student student);

mapper.xml

<update id="updateStudentByIdSelective">
    update student
    <!--
  suffixOverrides:去除指定的后缀
  prefix:增加指定的前缀
  suffix:增加指定的后缀
  prefixOverrides:去除指定的前缀
  -->
    <trim suffixOverrides="," prefix="set" suffix="" prefixOverrides="">
      <if test="student.name!=null">
            name = #{student.name},
        </if>
        <if test="student.clazz!=null">
          class=#{student.clazz},
        </if>
        <if test="student.score!=null">
          score=#{student.score},
        </if>
    </trim>
    <where>
      id = #{student.id}
    </where>
</update>   
• 22
• 23

这里要注意,添加和删除操作针对的是一整个被trim包含的语句,而并非里面的某一句if标签

set

set标签就相当于trim标签的<trim suffixOverrides="," prefix="set">

一般用于update中,这样可以使我们的操作更加方便

mapper

int updateStudentByIdSelectiveUseSet(@Param("student") Student student);

mapper.xml

<update id="updateStudentByIdSelectiveUseSet">
    update student
    <set>
      <if test="student.name!=null">
          name=#{student.name}
        </if>
        <if test="student.clazz!=null">
          class=#{student.clazz}
        </if>
        <if test="student.score!=null">
          score=#{student.score}
        </if>
        <where>
          id = #{student.id}
        </where>
    </set>
</update> 

sql-include

可以将sql片段提取出来重复利用

<sql id="base_column">
  id,class,name,score
</sql>

在调用的时候只需要写入标签名即可,可以代替*

<include refid="base_column"/>

foreach

可以帮助我们在SQL里面循环,一般用于批量的插入

插入一个集合或者数组

mapper

// 批量插入 有注解
int insertStduentList(@Param("studentList") List<Student> studentList);
// 批量插入 没有注解
int insertStduentListWithoutParam(List<Student> studentList);
// 批量插入 传入数组 有注解
int insertStudentArray(@Param("studentArray") Student[] students);
// 批量插入 传入数组 没有注解
int insertStudentArrayWithParam(Student[] students);

mapper.xml

<!--
    collection : 传入的集合的名字,假如集合参数没有注解,那么使用 collection | list | array(数组)|arg0(按位置传值)
  如果没有注解且使用集合的话,建议使用collection,包容性比较强
    item:指集合里面的元素
    separator: 分隔,指每一个元素之间用什么分隔
    open: 指定循环开头的字符
    close: 指定循环结束的字符
    index: 指下标
    -->
<insert id="insertStduentList">
    insert into student values
    <foreach collection="studentList" item = "stu" separator=",">
      (#{stu.id},#{stu.name},#{stu.clazz},#{stu.score})
    </foreach>
</insert>
<insert id="insertStduentListWithoutParam">
    insert into student values
    <foreach collection="collection" item="stu" separator=",">
      (#{stu.id},#{stu.name},#{stu.clazz},#{stu.score})
    </foreach>
</insert>
<insert id="insertStudentArray">
    insert into student values
    <foreach collection="studentArray" item="stud" separator=",">
      (#{stu.id},#{stu.name},#{stu.clazz},#{stu.score})
    </foreach>
</insert>
<insert id="insertStudentArrayWithParam">
  insert into student values
    <foreach collection="array" item = "stu" separator=",">
      (#{stu.id},#{stu.name},#{stu.clazz},#{stu.score})
    </foreach>
</insert>
in查询

mapper

// 根据Id的List去查询 StudentList
List<Student> selectStudentListByIds(@Param("ids") List<Integer> ids);

mapper.xml

<select id="selectStudentListByIds" resultMap="studentMap">
    select <include refid="base_column"/>
    from student
    where id in
    <foreach collection="ids" item="id" separator="," open="(",close=")">
      #{id}
    </foreach>
</select>

in传入数组和加不加注解和上面都是一致的

selectKey

可以帮助我们在执行对应的SQL语句之前或者之后执行一条额外的SQL语句,一般是用来获取自增的key值

mapper

// 插入一个Account,并且获取自增的id值
int insertAccount(@Param("account") Account account);

mapper.xml

<insert id="insertAccount">
  insert into account values(#{account.id},#{account.name},#{account.money},#{account.role})
        <!--
    resultType: SQL语句的返回值类型
    keyProperty:表示需要把结果封装到哪个参数里面去
    order:  AFTER:表示在插入的主SQL之前执行
            BEFORE:表示在插入的主SQL之后执行
    -->
    <selectKey resultType="int" keyProperty="account.id" order="AFTER">
      select LAST_INSERT_ID()
    </selectKey>
</insert>

这里要注意,获取的自增的id是当前插入的这个对象对应的id,并非是下一个自增的id是多少

方法的名字也表示是LAST

获取

方法的返回值依然是和接口中写入的一致,这里返回的自增的id被封装到对象中了,需要通过对象调用来获取

useGeneratedKeys

用来获取自增的主键值,直接写入insert标签中即可

mapper

// 插入一个Account,并且获取自增的id值
int insertAccountUseGeneratedKeys(@Param("account") Account account);

mapper.xml

<insert id = "insertAccountUseGeneratedKeys" useGeneratedKeys="true" keyProperty="account.id">
  insert into account values{#{student.id},#{student.name},#{student.money},#{student.role}}
</insert>

相当于是使用一个附加属性KeyProperty表示将获取到的key封装到指定的位置

多表查询

一对一

  • 建立模型
    用户表—>用户详情表
    User—>UserDetail
  • 创建javaBean
    User中额外维护一个UserDetail对象作为成员属性
  • 分次查询mapper
// 通过用户id查询用于信息以及用户详细信息
// 分次查询
User selectUserByIdWithUserDetail(@Param("id") Integer id);
  • mapper.xml
<resultMap id="userMap" type="com.cskaoyan.vo.User">
    <id column="id" property="id"/>
    <result column="username" property="username"/>
    <result column="nickname" property="nickname"/>
    <result column="gender" property="gender"/>
    <result column="age" property="age"/>
    <!-- 假如是单个对象 使用标签 association
        property: 指成员变量的名字
        JavaType:指成员变量的类型
        select:指第二次查询SQL语句的坐标,假如和当前ResultMap在同一个文件内,可以只写Id
        column:指我们要把哪一列的值传递给第二个SQL语句
    -->
    <association property="userDetail"
                 javaType="com.cskaoyan.vo.UserDetail"
                 select="selectUerDetailByUserId"
                 column="id"/>
</resultMap>
<select id="selectUerDetailByUserId" resultType="com.cskaoyan.vo.UserDetail">
    select id,user_id as userId,height,weight,pic from user_detail where user_id  = #{id}
</select>
<!-- 分次查询 -->
<!-- select * from user where id = ? -->
<!-- select * from user_detail where user_id  = ? -->
<select id="selectUserByIdWithUserDetail" resultMap="userMap">
    select <include refid="base_column"/>
    from user where id = #{id}
</select>
  • 总结分次查询的步骤:
  • 首先写查询入口,明确终极目标是返回一个什么值,并且要根据传入的参数过滤,创建一个resultMap用来映射返回结果
  • 编写resultMap,前面的成员属性正常映射即可,对象属性使用association标签来维护,如果是分次查询的话则该标签直接以/作为结束,property属性表示我们需要映射的对象,javaType表示该对象对应的全限定类名,select表示查询该对象的语句,column表示我们要从当前的列表中传入一列的值给第二个SQL语句作为关联,类似连表查询,一般是传id
  • 最后编写select第二个SQL语句,此时要注意列名是否与对象中的属性对应,如果不一致可以通过起别名或者resultMap映射来解决
  • 连接查询mapper
// 连接查询
    User selectUserByIdWithUserDetailUseCross(@Param("id") Integer id);
  • mapper.xml
<!-- 连接查询 -->
<resultMap id="userCrossMap" type="com.cskaoyan.vo.User">
    <id column="id" property="id"/>
    <result column="username" property="username"/>
    <result column="nickname" property="nickname"/>
    <result column="gender" property="gender"/>
    <result column="age" property="age"/>
    <association property="userDetail" javaType="com.cskaoyan.vo.UserDetail">
        <id column="did" property="id"/>
        <result column="userId" property="userId"/>
        <result column="height" property="height"/>
        <result column="weight" property="weight"/>
        <result column="pic" property="pic"/>
    </association>
</resultMap>
<select id="selectUserByIdWithUserDetailUseCross" resultMap="userCrossMap">
    SELECT
            user.id AS id,
            user.username AS username,
            user.nickname AS nickname,
            user.gender AS gender,
            user.age AS age,
            user_detail.id AS did,
            user_detail.user_id AS userId,
            user_detail.height AS height,
            user_detail.weight AS weight,
            user_detail.pic AS pic
    FROM
            user LEFT OUTER JOIN user_detail ON user.id = user_detail.user_id
    WHERE
            user.id = #{id}
</select>
  • 连接查询的步骤:
  • 写出连接查询的sql语句,并在navicat中检测是否有误
  • 根据列名来进行映射即可,要注意此时的association标签是成对出现的property表示关联的对象的成员名称,javaType表示该对象的全限定类名
  • 在association标签中直接根据查询的列名的结果对该对象进行映射,方式与普通对象一致

一对多

  • 建立模型

班级表---->学生表

  • 创建对象

在班级的对象中维护一个studentList,表示一个班有多个学生,即维护在一的一方;在学生的属性中维护一个班级id,依次达到连接的效果

  • 分次查询
    mapper
// 一对多的分次查询
// 通过班级Id查询出班级的信息以及班级的学生信息
Clazz selectClazzById(@Param("id") Integer id);
  • mapper.xml
<!-- 映射关系 -->
<resultMap id="clazzMap" type="com.cskaoyan.vo.Clazz">
    <id column="id" property="id"/>
    <result column="name" property="name"/>
    <collection property="studentList"
                ofType="com.cskaoyan.vo.Student"
                select="selectStduentListByClazzId"
                column="id"/>
</resultMap>
<!-- 第二次查询 -->
<select id="selectStduentListByClazzId" resultType="com.cskaoyan.vo.Student">
    select id,name,gender,age,clazz_id as clazzId from student where clazz_id = #{id}
</select>
<!-- 一对多的分次查询入口 -->
<select id="selectClazzById" resultMap="clazzMap">
    select id,name from clazz where id = #{id}
</select>

分次查询的步骤:

  • 先写出入口,SQL语句过滤出要查询的班级的信息,使用resultMap映射
  • 在resultMap中将班级的信息映射完毕后,studentList需要使用collection标签来进行关联,这里采用分次查询,所以直接用/结尾,property还是填要关联的对象属性名,注意集合类中要使用ofType来表示该对象的全限定类名,select语句表示查询该对象的语句,column表示传给第二个语句的值,这里是根据班级号来查询,所以传id
  • 写第二个查询语句,我们在第一个查询语句中传出的值是班级id,所以第二个语句要根据学生表中保存的班级id来过滤,得到的最终结果直接封装进对象即可,如果使用resultType要写全限定类名,列表不同需要起别名,否则就要使用resultMap进行映射

连接查询

mapper

// 一对多的连接查询
Clazz selectClazzByIdUseCrossQuery(@Param("id") Integer id);

mapper.xml

<resultMap id="clazzCrossMap" type="com.cskaoyan.vo.Clazz">
    <id column="id" property="id"/>
    <result column="name" property="name"/>
    <collection property="studentList" ofType="com.cskaoyan.vo.Student">
        <id column="sid" property="id"/>
        <result column="sname" property="name"/>
        <result column="age" property="age"/>
        <result column="gender" property="gender"/>
        <result column="clazzId" property="clazzId"/>
    </collection>
</resultMap>
<!-- 连接查询 -->
<select id="selectClazzByIdUseCrossQuery" resultMap="clazzCrossMap">
    SELECT
            clazz.id AS id,
            clazz.name AS name,
            student.id AS sid,
            student.NAME AS sname,
            student.age AS age,
            student.gender AS gender,
            student.clazz_id AS clazzId
    FROM
            clazz
                    LEFT OUTER JOIN student ON clazz.id = student.clazz_id
    WHERE
            clazz.id = #{id}
</select>

连接查询的步骤:基本与一对一的情况一致,唯一要注意的就是collection标签中要用ofType表示集合中单个对象的全限定类名

多对多

  • 建立模型
    学生表—>选课表---->课程表
  • 创建javaBean
    在学生的对象中维护一个课程的列表表示选课信息,注意不能相互维护,否则会无限循环
  • 分次查询
// 分次查询  根据学生id查询出学生信息以及其课程信息
Student selectStudentWithCourseListById(@Param("id") Integer id);

mapper.xml

<!-- 映射关系 -->
<resultMap id="studentMap" type="com.cskaoyan.vo.Student">
    <id column="id" property="id"/>
    <result column="name" property="name"/>
    <result column="gender" property="gender"/>
    <result column="age" property="age"/>
    <result column="clazz_id" property="clazzId"/>
    <collection property="courseList"
                ofType="com.cskaoyan.vo.Course"
                select="selectCourseListByClazzId"
                column="id"/>
</resultMap>
<!-- 第二次查询 -->
<select id="selectCourseListByClazzId" resultType="com.cskaoyan.vo.Course">
    SELECT
            c.id as id,
            c.name as name,
            c.teacher_name as teacherName,
            c.score as score
    FROM
            s_c AS sc
                    LEFT JOIN course AS c ON sc.cid = c.id
    WHERE
            sc.sid = #{id}
</select>
<!-- 分次查询的入口-->
<select id="selectStudentWithCourseListById" resultMap="studentMap">
    select id,name,gender,age,clazz_id from student where id = #{id}
</select>

分次查询的步骤:与一对多基本一致,在第二个语句中要注意明确需要返回什么,然后进行连表查询

  • 连接查询
    mapper
// 连接查询 根据学生id查询出学生信息以及其课程信息
Student selectStudentWithCourseListByIdUseCrossQuery(@Param("studentId") Integer studentId);

mapper.xml

<resultMap id="studentCrossMap" type="com.cskaoyan.vo.Student">
    <id column="sid" property="id"/>
    <result column="sname" property="name"/>
    <result column="age" property="age"/>
    <result column="gender" property="gender"/>
    <result column="clazzId" property="clazzId"/>
    <collection property="courseList" ofType="com.cskaoyan.vo.Course">
        <id column="cid" property="id"/>
        <result column="cname" property="name"/>
        <result column="teacherName" property="teacherName"/>
        <result column="score" property="score"/>
     </collection>
</resultMap>
<!-- 连接查询 -->
<select id="selectStudentWithCourseListByIdUseCrossQuery" resultMap="studentCrossMap">
    SELECT
            student.id AS sid,
            student.name AS sname,
            student.age AS age,
            student.gender AS gender,
            student.clazz_id AS clazzId,
            course.id AS cid,
            course.name AS cname,
            course.teacher_name AS teacherName,
            course.score AS score
    FROM
            student
                    LEFT JOIN s_c ON student.id = s_c.sid
                    LEFT JOIN course ON s_c.cid = course.id
    WHERE
            student.id = #{studentId}
</select>

懒加载

在进行多表的分次查询的时候,假如第二次查找的信息我们暂时不需要的话,就可以暂时不执行,等到需要的时候再执行查询,可以提高性能

懒加载有总开关和局部开关,当冲突的的时候以局部开关为准

  • 总开关
    表示开启全局的懒加载
<!-- 懒加载  总开关-->
<setting name="lazyLoadingEnabled" value="true"/>
  • 局部开关
    在association标签中增加一个属性,fetchType=‘’
    eager:关闭懒加载
    lazy:开启懒加载

缓存

一级缓存

Mybatis的一级缓存是一个SQLSession级别的缓存,默认是开启的,并且Mybatis没有给我们提供关闭一级缓存的方式。

在使用同一个SQLSession去查询同样的数据的时候,第一次查询会查询数据库,然后将这个查询的结果保存到属于自己的内存空间里面,当再次查询的时候如果参数一样,会直接返回内存里的内容,不必再次查询数据库。当执行SQLSession.commit()的时候一级缓存会被清空

一级缓存失效的场景

  • 使用不同的SQLSession
  • SQLSession提交或者关闭

二级缓存

Mybatis的二级缓存是一个Namespace级别的缓存,默认是关闭的。

总开关:

<!-- 二级缓存总开关  默认值是true-->
<setting name="cacheEnabled" value="true"/>

局部缓存:

开启对应的mapper.xml中局部缓存

仅仅只需要在mapper.xml中加上一个标签即可

<cache/>
  • 我们需要对对应的对象实现序列化接口

Mybatis的一级缓存整体上来说是稍微有一点用的,可以帮助我们减轻一部分数据库的压力。但是Mybatis的二级缓存容易出现一些脏数据问题,所以我们在工作中一般不使用二级缓存

  • 二级缓存不可靠(理论上可以出现脏数据的问题)
  • 二级缓存不可控
    二级缓存区的内容,我们用户不能够自己去手动的修改与维护,所以其实对于使用者来说,二级缓存区的内容是透明的,也是不可控的。
  • 二级缓存功能不够强大
    我们不能对二级缓存里面的内容去做一些特定的功能,例如使用者想让对应的缓存定时过期,定时刷新等等
相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
1月前
Mybatis+mysql动态分页查询数据案例——分页工具类(Page.java)
Mybatis+mysql动态分页查询数据案例——分页工具类(Page.java)
|
16天前
|
SQL Java 关系型数据库
Java中的ORM框架——myBatis
Java中的ORM框架——myBatis
|
1月前
|
SQL druid Java
java mysql druid mybatis-plus里使用多表删除出错的一种处理方式
java mysql druid mybatis-plus里使用多表删除出错的一种处理方式
24 0
|
1月前
Mybatis+mysql动态分页查询数据案例——工具类(MybatisUtil.java)
Mybatis+mysql动态分页查询数据案例——工具类(MybatisUtil.java)
|
1月前
|
XML Java 数据库连接
Java一分钟之MyBatis:持久层框架基础
【5月更文挑战第15天】MyBatis是Java的轻量级持久层框架,它分离SQL和Java代码,提供灵活的数据库操作。常见问题包括:XML配置文件未加载、忘记关闭SqlSession、接口方法与XML映射不一致、占位符使用错误、未配置ResultMap和事务管理不当。解决这些问题的关键在于正确配置映射文件、管理SqlSession、避免SQL注入、定义ResultMap以及确保事务边界。遵循最佳实践可优化MyBatis使用体验。
30 2
Java一分钟之MyBatis:持久层框架基础
|
1月前
|
SQL Java
【JAVA进阶篇教学】第九篇:MyBatis-Plus用法介绍
【JAVA进阶篇教学】第九篇:MyBatis-Plus用法介绍
|
1月前
|
SQL Java 数据库连接
15:MyBatis对象关系与映射结构-Java Spring
15:MyBatis对象关系与映射结构-Java Spring
48 4
|
1月前
|
SQL Java 数据库连接
Java从入门到精通:3.1.2深入学习Java EE技术——Hibernate与MyBatis等ORM框架的掌握
Java从入门到精通:3.1.2深入学习Java EE技术——Hibernate与MyBatis等ORM框架的掌握
|
1月前
|
XML Java 数据库连接
java对象有集合mybatis如何映射
java对象有集合mybatis如何映射
28 4
|
1月前
|
Java 数据库连接 mybatis
mybatis简单案例源码详细【注释全面】——Utils层(MybatisUtils.java)
mybatis简单案例源码详细【注释全面】——Utils层(MybatisUtils.java)