【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>
  • 第三步:配置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>

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

别名映射的类型
_bytebyte
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator

环境配置

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

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

这里要注意,添加和删除操作针对的是一整个被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的二级缓存容易出现一些脏数据问题,所以我们在工作中一般不使用二级缓存

  • 二级缓存不可靠(理论上可以出现脏数据的问题)

  • 二级缓存不可控

    二级缓存区的内容,我们用户不能够自己去手动的修改与维护,所以其实对于使用者来说,二级缓存区的内容是透明的,也是不可控的。

  • 二级缓存功能不够强大

    我们不能对二级缓存里面的内容去做一些特定的功能,例如使用者想让对应的缓存定时过期,定时刷新等等

  • 17
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java插件MyBatis是一种用于Java开发的ORM开源框架。它提供了简单易用的插件扩展机制,可以对MyBatis的四大核心对象(Executor、StatementHandler、ParameterHandler、ResultSetHandler)进行拦截和增强功能。这些核心对象在MyBatis中都是代理对象,通过插件可以对其功能进行扩展和定制。你可以在官方链接和GitHub上找到Java MyBatis SQL Scanner插件的详细信息和使用教程。这个插件可以帮助你更方便地使用MyBatis进行SQL扫描和处理。123 #### 引用[.reference_title] - *1* [idea插件——Java Mybatis SQL Scanner(已开源)](https://blog.csdn.net/q258523454/article/details/123094358)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}} ] [.reference_item] - *2* [mybatis 插件](https://blog.csdn.net/jason559/article/details/120368131)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}} ] [.reference_item] - *3* [Mybatis插件](https://blog.csdn.net/weixin_52851967/article/details/125190987)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值