Mybatis的基础使用

一.简介

MyBatis 是一个半自动化的 ORM(对象关系映射)框架,允许开发者手动编写 SQL 语句,从而能够更好地控制 SQL 的执行,提高性能。MyBatis 可以将 SQL 语句与 Java 代码分离,存储在 XML 文件或者注解中,让代码结构更清晰。

二.初步使用

1.导入依赖

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>

2.创建Do对象

此处为基本数据对象

@Data
public class EmpDo {
    private Long id;
    private String name;
    private Integer age;
    private String email;
    private String gender;
    private String address;
    private BigDecimal salary;
}

3.Dao层创建Mapper接口

@Mapper注解告诉mybatis这是一个Mapper接口

@Mapper
public interface EmpMapper {
    EmpDo getEmpById(Integer id);
}

4.生成xml配置文件

此处可以安装mybatisx插件自动生成一个文件

namespace写Mapper对象的全类名

<?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.zhang.mybatis.dao.EmpMapper">
    //此处添加sql方法
</mapper>

还需要在application.properties中指明xml配置文件的位置

mybatis.mapper-locations=classpath:mapper/**.xml

5.sql方法的编写

<select id="getEmpById" resultType="com.zhang.mybatis.bean.EmpDo">
    select * from employee where id = #{id}
</select>
<insert id="addEmp">
    insert into employee(last_name,email,gender) values (#{lastName},#{email},#{gender})
</insert>
<update id="updateEmp">
    update employee set last_name = #{lastName},email = #{email},gender = #{gender} where id = #{id}
</update>
<delete id="deleteEmpById">
    delete from employee where id = #{id}
</delete>
  • id对应Mapper对象的方法名

  • resultType对应返回值

  • 括号间为sql语言

接下来就可以直接调用Mapper方法了

6.开启自增id

<insert id="addEmp" useGeneratedKeys="true" keyProperty="id"></insert>
  • useGeneratedKeys表示是否开始自增id

  • keyProperty指定自动生成的id在Do中对应的属性,把自动生成的id封装回Emp对象的id属性中

7.命名规则转化

java和mysql数据库采用了不同的命名规则

  • java采用驼峰命名(userName)

  • 数据库采用蛇形命名(user_name)

在配置文件中开启命名规则自动转换,才能使名称一 一对应上

mybatis.configuration.map-underscore-to-camel-case=true

三.参数

1.占位符#{}和${}的区别

select * from employee where id = #{id}

select * from employee where id = ${id}
  • #{} :这是MyBatis中预编译的占位符,用于将参数值安全地插入到SQL语句中。在SQL执行前,MyBatis会将#{} 替换为?,然后使用PreparedStatement来设置参数值,有效防止了sql注入问题。

    Connection connection = dataSource.getConnection();
    String sql = "select * from employee where id=?";
    PreparedStatement ps = connection.prepareStatement(sql);
    ps.setString(1, "admin");
  • ${}:这是字符串替换占位符,MyBatis会直接将${} 替换为实际的参数值,而不进行预编译,会引发sql注入问题。

  • 只有参数位置动态取值才可以用#{},jdbc层面表名等位置不支持预编译,只能用${}

  • 总结:能用#{}大括号的地方就用,不能用#{}的地方用${}

2.单参数取值

  • 对象取值:直接写属性名,不要写对象名

select * from employee where id = #{name}
  • list取值:使用索引

select * from employee where id = #{ids[5]}
  • map取值: 使用key值

select * from employee where id = #{myId}

3.多参数取值

新版mybatis支持直接使用#{参数名}

老版mybatis需要在参数前使用@Param指定参数名

Emp getEmployByIdAndName(@Param("id") Long id, @Param("empName") Employee emp)
select * from Employee where id = #{id} and name = #{emp.name}

四.返回值

1.一般返回:

<select id="getEmpById" resultType="com.zhang.mybatis.bean.EmpDo">
    select * from employee where id = #{id}
</select>

resultType规定返回值类型

2.返回map:

mapper方法中使用@Mapper("")指定key值,key默认从对象属性值中取

@Mapper("id")//将id作为key值
Map<Integer,Emp> getAllMap();

返回值的value实际上不是指定类(Emp),而是一个HashMap,

resultType不写map,写指定类,value值才为指定类

3.封装规则

默认封装规则(resultType):JavaBean中的属性名 去数据库表中 找对应列名的值,一一映射封装。

自定义封装规则(resultMap):明确指定每一列如何封装到JavaBean中

在xml中写一个ResultMap对照表

<resultMap id="EmpRM" type="com.zhang.mybatis.bean.EmpDo">
    <!--声明主键映射规则-->
        <id property="id" column="id"></id>
    <!--声明普通列映射规则-->
        <result property="name" column="name"></result>
        <result property="age" column="age"></result>
</resultMap>

在方法中根据ResultMap的id使用对照规则

<select id="getEmpById" resultMap="EmpRM">
    select * from employee where id = #{id}
</select>

五.关联查询

1.association单一关联封装

<select id="getOrderById" resultType="com.zhang.mybatis.bean.Order">
    select * from orders
    left join customer on orders.customer_id = customer.id
    where orders.id = #{id}
</select>

使用默认封装(resultType)无法将连接的对象正确装填(customer将返回为null)

Order(id=1, address=chengdu, amount=5000, customerId=1, customer=null)

这时候要在自定义封装(resultMap)中使用association标签

<resultMap id="OrderRM" type="com.zhang.mybatis.bean.Order">
    <id property="id" column="id"></id>
    <result property="address" column="address"></result>
    <result property="amount" column="amount"></result>
    <result column="customer_id" property="customerId"></result>
    <association property="customer" javaType="com.zhang.mybatis.bean.Customer">
        <id property="id" column="id"></id>
        <result property="name" column="name"></result>
    </association>
</resultMap>

左连接时,association标签中定义了右表的封装关系

2.collection集合关联封装

property对应集合,ofType对应单一类

<resultMap id="CustomerRM" type="com.zhang.myb atis.bean.Customer">
    <id property="id" column="id"></id>
    <result property="name" column="name"></result>
    <collection property="orders" ofType="com.zhang.mybatis.bean.Order">
        <id property="id" column="id"></id>
        <result property="address" column="address"></result>
        <result property="amount" column="amount"></result>
        <result column="customer_id" property="customerId"></result>
    </collection>
</resultMap>

六.分步查询

1.手动分步查询

//按照id查询订单
Order order = orderDao.getOrderById(1);
//使用订单中的用户id查询用户
Customer customer = OrderDao.getCustomerById(order.getCustomerId());
//将用户设置到订单中
order.setCustomer(customer);

2.自动分步查询

<resultMap id="OrderRM" type="com.zhang.mybatis.bean.Order">
        <id property="id" column="id"></id>
        <result property="address" column="address"></result>
        <result property="amount" column="amount"></result>
        <result property="customerId" column="customer_id"></result>
        
        <collection property="customers"
                    select="com.zhang.mybatis.dao.OrderDao.getCustomerById"
                    column="customer_id">
        </collection>
        
    </resultMap>
  • collection或者association中使用select属性告诉mybatis在装填该项时使用另一个查询方法

  • column指定传递的参数,当出现多个参数时,使用以下格式

    column="{userName=user_name,userAge=user_age}"

3.超级分步问题

  • 问题:当实体类之间存在循环引用时,例如 A 类关联 B 类,B 类又关联 A 类,分步查询可能会陷入无限循环,最终导致栈溢出错误

  • 解决办法:使用嵌套查询,把分步查询改成嵌套查询,在一个 SQL 语句里完成所有关联查询。不过这样可能会让 SQL 语句变得复杂,降低可读性。

4.延迟加载

  • MyBatis 的延迟加载基于 Java 的动态代理机制。当主查询执行时,关联对象会被代理对象替代。只有当真正访问关联对象的属性或方法时,MyBatis 才会触发关联查询,从数据库中获取实际数据。

  • 开启延迟加载:

    在application.properties中添加配置

    mybatis.configuration.lazy-loading-enabled=true//开启 MyBatis 的延迟加载特性。
    mybatis.configuration.aggressive-lazy-loading=false//禁用积极延迟加载。

七.动态sql

默认事务回滚开启

1.if标签

当test中的条件成立时,标签中的语句才存在

<select id="getOrderById" resultMap="OrderRM">
    select * from orders
    <if test="id != null and id!='' ">
        where orders.id = #{id}
    </if>
</select>

2.where标签

将if标签等都放到where中,会自动解决where后面语法错误问题

<select id="getOrderById" resultMap="OrderRM">
    select * from orders
    <where>
    	<if></if>
    	<if></if>
    </where>
</select>

3.set标签

和where标签相同,解决语法问题

<update id="updateEmp">
	update t_emp
	 <set>
	 	<if></if>
	 	<if></if>
	 </set>
<update>

4.trim标签

当标签中存在内容时,为该内容添加前缀(prefix)或者后缀(suffix)

<trim prefix="前缀" suffix="后缀"></trim>

标签体中最终生成的字符串如果以指定前缀(prefixOverrides)或者后缀(suffixOverrides)开始,就将该前缀覆盖为空串

<trim prefixOverrides="前缀" suffixOverrides="后缀"></trim>

5.choose/when/otherwise标签

<choose>:作为根标签,包裹多个 <when> 和一个可选的 <otherwise> 标签,用于表示一组条件选择逻辑。

<choose>
    <when test="condition1">
        <!-- SQL 片段 1 -->
    </when>
    <when test="condition2">
        <!-- SQL 片段 2 -->
    </when>
    <!-- 可以有多个 <when> 标签 -->
    <otherwise>
        <!-- 默认 SQL 片段 -->
    </otherwise>
</choose>

6.foreach标签

  • IN子句:查询多ID记录,生成如 (id1, id2)。

    <select id="getOrdersByIds" resultMap="OrderRM">
        SELECT * FROM orders
        WHERE id IN
        <foreach item="item" index="index" collection="orderIds"
                 open="(" separator="," close=")">
            #{item}
        </foreach>
    </select>
    
  • 批量插入:生成多个 VALUES 子句。

    <insert id="batchInsertOrders">
        INSERT INTO orders (address, amount, customer_id)
        VALUES
        <foreach item="order" index="index" collection="orderList"
                 separator=",">
            (#{order.address}, #{order.amount}, #{order.customerId})
        </foreach>
    </insert>
  • 批量更新:结合 CASE WHEN 语句。

    <update id="batchUpdateOrders">
        <foreach collection="emps" item="e" separator=";">
        	update t_emp set emp_name = #{e.empName} where id = #{e.id}
        </foreach>
    </update>

7.抽取复用sql片段

  • 抽取:

    <sql id="column_names"></sql>
  • 引用:

    <include refid="column_names"></include>

八.xml的转义字符

字符转义序列描述
&&amp;用于表示字符 & 本身,& 在 XML 中有特殊含义,用于表示转义序列的开始,所以需要转义
<&lt用于表示小于号 << 用于标识 XML 标签的开始,因此在文本中使用时需转义
>&gt用于表示大于号 >> 用于标识 XML 标签的结束,在文本中使用时需转义
"&quot用于表示双引号 " ,在属性值中常用双引号,当在文本内容中出现双引号时需转义
'&apos用于表示单引号 ' ,在属性值中也可以使用单引号,在文本内容中出现单引号时也需转义

九.mybatis缓存机制

一级缓存

  • 作用域:一级缓存也称为会话(SqlSession)级缓存,它的作用域是单个 SqlSession。在同一个 SqlSession 中,执行相同的 SQL 查询时,MyBatis 会优先从一级缓存中获取数据,而不是再次查询数据库。

  • 原理:当一个 SqlSession 执行查询操作时,MyBatis 会将查询结果存储在该 SqlSession 的缓存中。如果后续再次执行相同的查询(相同的 SQL 语句和参数),MyBatis 会直接从缓存中返回结果,而不会再次访问数据库。

  • 失效情况:当 SqlSession 执行插入、更新、删除操作,或者调用 SqlSession 的 clearCache() 方法时,一级缓存会被清空。

  • 示例代码:

    try (SqlSession session = sqlSessionFactory.openSession()) {
        UserMapper mapper = session.getMapper(UserMapper.class);
        // 第一次查询
        User user1 = mapper.getUserById(1); 
        // 第二次查询,使用缓存
        User user2 = mapper.getUserById(1); 
        System.out.println(user1 == user2); // 输出 true
    }

二级缓存

  • 作用域:二级缓存也称为命名空间(Mapper)级缓存,它的作用域是同一个命名空间(Mapper 接口)。不同的 SqlSession 可以共享二级缓存中的数据,只要它们操作的是同一个 Mapper 接口。

  • 原理:MyBatis 的二级缓存是基于 Mapper 命名空间的,每个 Mapper 都有自己独立的二级缓存。当一个 SqlSession 执行查询操作并将结果存储在二级缓存中后,其他 SqlSession 执行相同的查询时,可以直接从二级缓存中获取数据。

  • 开启方式:要开启二级缓存,需要在 Mapper XML 文件中添加 <cache> 标签,或者在 Mapper 接口上添加 @CacheNamespace 注解。

  • 失效情况:当 Mapper 对应的 SqlSession 执行插入、更新、删除操作时,该 Mapper 的二级缓存会被清空。

  • 示例代码

    <mapper namespace="com.example.mapper.UserMapper">
        <!-- 开启二级缓存 -->
        <cache /> 
        <select id="getUserById" resultType="com.example.entity.User">
            SELECT * FROM user WHERE id = #{id}
        </select>
    </mapper>
    try (SqlSession session1 = sqlSessionFactory.openSession();
         SqlSession session2 = sqlSessionFactory.openSession()) {
        UserMapper mapper1 = session1.getMapper(UserMapper.class);
        UserMapper mapper2 = session2.getMapper(UserMapper.class);
        // 第一次查询,存入二级缓存
        User user1 = mapper1.getUserById(1); 
        // 第二次查询,从二级缓存获取
        User user2 = mapper2.getUserById(1); 
        System.out.println(user1 == user2); // 输出 false,但数据内容相同
    }

十.插件机制

MyBatis底层使用拦截器机制提供插件功能,方便用户在SQL执行前后进行拦截增强。

拦截器:Interceptor

它允许拦截 Executor(执行器)、ParameterHandler(参数处理器)、ResultSetHandler(结果集处理器)和 StatementHandler(语句处理器)这四个核心接口的方法调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值