mybatis

59 阅读4分钟

准备工作

  • 相关maven依赖 Lombok Spring Web MyBatis Framework MySQL Driver

  • application.properties 配置数据源

spring.datasource.url=jdbc:mysql://localhost:3306/myapp_db
spring.datasource.username=xxx
spring.datasource.password=xxx
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver


# resources目录下新建mapper目录 
mybatis.mapper-locations=classpath:mapper/**.xml
# 数据库表列明 中横线转 表实体bean对应的小驼峰
mybatis.configuration.map-underscore-to-camel-case=true
# 开启sql日志
logging.level.org.example.mybatis.mapper=debug
  • idea安装mybatisx插件, 按键自动生成Mapper接口对应的xml实现
  • @Mapper 增删改查接口 或者 @MapperScan扫描
@MapperScan({"org.example.mybatisgenerate.mapper"})
@Configuration
public class MybatisConfig {
}

取值

#{}安全的参数预编译
${}直接的字符串拼接。

特性#{} (井号占位符)${} (美元占位符)
处理方式预编译(PreparedStatement) ,参数化查询字符串拼接(Statement) ,直接替换
安全性,有效防止SQL注入,存在SQL注入风险
参数类型任意类型,自动进行类型转换和转义通常为字符串,原样替换,不转义
使用场景几乎所有的值传递(WHERE条件值、INSERT值等)动态SQL片段(表名、列名、ORDER BY等)

取入参值

  • 当只有一个参数时,直接放变量名(xml中的变量名可以随便命名) 或 bean中属性, 这时可以不用@Param;若用了@Param,xml中的变量名必须和@Param内的命名保持一致,不能直接取bean中属性,需要@Param内的命名.属性名
@Mapper
public interface UserMapper {

    User getUserById(String id);

    void insertUser(User user);

}

<select id="getUserById" resultType="org.example.mybatis.entity.User">
    select * from users where id = #{id}
</select>

<!-- username  email 为 User 中的属性 -->
<insert id="insertUser">
    insert into users(username, email)values (#{username}, #{email})
</insert>
  • 多个参数用@Param
void updateUser(@Param("id") String id, @Param("us") User user);

<update id="updateUser">
    update users
    set username = #{us.username},
        email    = #{us.email}
    where id = #{id}
</update>

最佳实践:为了统一化取参,即使一个参数也使用@Param

返回值处理

返回普通数据 resultType

  • 返回基本类型、String、Long、普通javeBean都只需要在 resultType中声明返回值的全限定类名
  • mybatis.configuration.map-underscore-to-camel-case=true 开启驼峰映射:表中查询结果集中a_column列会自动被映射为bean中的aColumn属性

自定义映射结果集 resultMap

当表中结果和javabean属性不能映射(字段名不对应,即使开启驼峰映射也不行),这时需要使用自定义映射


<!-- id自定义的结果映射唯一名 type-接受结果bean的全类名 -->
<resultMap id="UserRm" type="org.example.mybatis.entity.User">
    <!-- id标签-主键 result标签-其他普通列 column-结果表的列名 property-bean的属性名 -->
    <id column="id" property="id"/>
    <result column="user_phone" property="phone"/>
</resultMap>

<select id="getUserByIdRm" resultMap="UserRm">
    select * from users;
</select>
  • 一对一,当遇到某个属性的类型是另一个javaBeanresultMap里面需使用association

@Data
public class Order {
    private String id;
    private String productName;
    private User user;
}

Order getOrderWithUserById(@Param("id") String id);

<resultMap id="OrderRm" type="org.example.mybatis.entity.Order">
    <id column="id" property="id"/>
    <result column="product_name" property="productName"/>
    <!-- 一对一,当遇到某个属性类是另一个javabean,需使用association。-->
    <association property="user" javaType="org.example.mybatis.entity.User">
        <id column="user_id" property="id"/>
        <result column="user_phone" property="phone"/>
        <result column="username" property="username"/>
        <result column="email" property="email"/>
    </association>
</resultMap>

<select id="getOrderWithUserById" resultMap="OrderRm">
    select orders.*, username, email, user_phone from orders left join users on orders.user_id = users.id
    where orders.id = #{id}
</select>
  • 一对多,当遇到某个属性的类型是另一个javaBean的集合resultMap里面需使用collection
@Data
public class User {
    private Integer id;
    private String username;
    private String email;
    private Date createdAt;
    private String phone;
    private List<Order> orderList;


    public User(String username, String email) {
        this.username = username;
        this.email = email;
    }
}

User getUserAllOrders(String id);

<resultMap id="getUserAllOrdersRm" type="org.example.mybatis.entity.User">
    <id column="id" property="id"/>
    <result column="username" property="username"/>
    <result column="email" property="email"/>
    <result column="user_phone" property="phone"/>
    <collection property="orderList" ofType="org.example.mybatis.entity.Order">
        <id column="orderId" property="id"/>
        <result column="product_name" property="productName"/>
    </collection>
</resultMap>
<select id="getUserAllOrders" resultMap="getUserAllOrdersRm">
    select users.*, orders.product_name, orders.id orderId
    from users
             left join orders on users.id = orders.user_id
    where users.id = #{id};    </select>

动态标签

  • <if> 标签

    作用:根据条件判断是否包含某段SQL。
    属性test (OGNL表达式,判断条件)

<!-- 根据条件查询用户列表 -->
<select id="selectUsers" resultType="User">
    SELECT * FROM sys_user 
    WHERE 1=1
    <!-- 如果username参数不为null且不为空字符串,则添加条件 -->
    <if test="username != null and username != ''">
        AND username LIKE CONCAT('%', #{username}, '%')
    </if>
    <if test="email != null and email != ''">
        AND email = #{email}
    </if>
    <if test="status != null">
        AND status = #{status}
    </if>
</select>
  • <where> 标签

    作用:自动处理WHERE关键字,并移除开头的AND/OR。

<select id="selectUsersSmart" resultType="User">
    SELECT * FROM sys_user 
    <where>
        <if test="username != null and username != ''">
            username LIKE CONCAT('%', #{username}, '%')
        </if>
        <if test="email != null and email != ''">
            AND email = #{email}
        </if>
        <if test="status != null">
            AND status = #{status}
        </if>
    </where>
</select>
  • <foreach> 标签:遍历集合

    作用:遍历集合(List、Array、Map),常用于IN查询或批量操作。
    核心属性

属性作用示例值
collection要遍历的集合参数名idListarraylist
item遍历时的每个元素别名iditem
index遍历的索引(可选)index
open整个循环开始时的字符串(
close整个循环结束时的字符串)
separator每次循环间的分隔符,
<!-- 根据多个ID查询用户 (IN查询) -->
<select id="selectUsersByIds" resultType="User">
    SELECT * FROM sys_user 
    WHERE id IN 
    <foreach collection="idList" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

<!-- 批量插入用户 -->
<insert id="batchInsert">
    INSERT INTO sys_user (username, email, age) VALUES 
    <foreach collection="userList" item="user" separator=",">
        (#{user.username}, #{user.email}, #{user.age})
    </foreach>
</insert>
  • <set> 标签

    作用:动态生成SET子句,自动去掉末尾的逗号。

<update id="updateUserSelective">
   UPDATE sys_user 
   <set>
       <if test="username != null and username != ''">
           username = #{username},
       </if>
       <if test="email != null">
           email = #{email},
       </if>
       <if test="age != null">
           age = #{age},
       </if>
       <if test="status != null">
           status = #{status}
       </if>
   </set>
   WHERE id = #{id}
</update>
  • <sql> 和 <include>

    用于SQL片段复用的利器。它们能有效消除冗余,提升代码的可维护性。[抽取字段列表 + 抽取复杂查询条件]

<sql id="base_user_column">
   id, username, user_phone as phone, email
</sql>

<sql id="base_user_query_condition">
   <where>
       <if test="user.username != null and user.username != '' ">
           username LIKE CONCAT('%', #{user.username}, '%')
       </if>
       <if test="user.phone != null and user.phone != ''">
           and user_phone = #{user.phone}
       </if>
       <if test="user.email != null and user.email != ''">
           and email = #{user.email}
       </if>
   </where>
</sql>

<select id="filterUsers" resultType="org.example.mybatis.entity.User">
   select
       <include refid="base_user_column"/>
       from users
       <include refid="base_user_query_condition"/>
</select>

pagehelper 分页插件

引入分页插件依赖

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>6.1.0</version>
</dependency>

配置拦截器插件

@Configuration
public class MyBatisConfig {

    @Bean
    PageInterceptor pageInterceptor(){
        return new PageInterceptor();
    }
}

使用分页[对于查询语句自动开启分页limit]

PageHelper.startPage(2, 3);
User user = new User();
List<User> users = userMapper.filterUsers(user);
log.info("users:{}", users);
PageInfo<User> userPageInfo = new PageInfo<>(users);
log.info("userPageInfo:{}", userPageInfo);
log.info("total:{}", userPageInfo.getTotal());
log.info("pageNum:{}", userPageInfo.getPageNum());
List<User> list = userPageInfo.getList();

可以看到sql变成

SELECT count(0) FROM users;
select id, username, user_phone as phone, email from users LIMIT ?, ?

根据表自动生成mapper 和 表实体

image.png