Mybatis(Plus)

1. Mybatis

MyBatis 是一个开源的Java持久层框架,用于将对象与关系数据库的表之间进行映射。MyBatis 通过 XML或注解配置文件描述 Java 对象与数据库之间的映射关系,并提供了一些方便的查询语言(类似于SQL)来进行数据库操作。
使用 MyBatis 来操作 MySQL 数据库,将数据存储在 MySQL 中,或从 MySQL 中检索数据,同时使用 MyBatis 进行数据映射和数据库操作的管理。它们通常一起使用,以构建 Java 应用程序的持久层。

数据映射:

MyBatis 的核心功能之一是提供简单且强大的数据映射。使用 XML或注解来定义 SQL查询和映射结果,将数据库表记录映射到 Java对象。

  • 1.1 XML 映射文件:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!-- 定义查询 -->
    <select id="selectUser" resultType="User">
    SELECT * FROM users WHERE id = #{id}
    </select>
    <!-- 映射结果到对象 -->
    <resultMap id="BaseResultMap" type="User">
    <id column="id" property="id"/>
    <result column="username" property="username"/>
    <result column="password" property="password"/>
    </resultMap>
  • 1.2 注解方式
    1
    2
    @Select("SELECT * FROM users WHERE id = #{id}")
    User selectUser(int id);

核心功能:

  • 动态 SQL:MyBatis 允许你在 XML 中编写动态 SQL 语句,可以根据条件动态构建 SQL 查询。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <select id="selectUsers" parameterType="map" resultType="User">
    SELECT * FROM users
    WHERE 1=1
    <if test="username != null">
    AND username = #{username}
    </if>
    <if test="password != null">
    AND password = #{password}
    </if>
    </select>
  • 参数传递:MyBatis 支持多种参数传递方式,包括单个参数、多个参数、Map 和注解等。
    1
    2
    @Select("SELECT * FROM users WHERE id = #{id} AND username = #{username}")
    User selectUserByIdAndUsername(@Param("id") int id, @Param("username") String username);
  • 批处理:MyBatis 允许执行批处理操作,可以有效地执行一组 SQL 语句。
    1
    2
    3
    4
    5
    6
    7
    8
    SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    for (User user : userList) {
    userMapper.insertUser(user);
    }
    sqlSession.flushStatements();
    sqlSession.commit();
    sqlSession.close();

事务管理:

  • MyBatis 也提供了事务管理的支持。可以通过配置数据源和事务管理器来实现事务的控制。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!-- 数据源配置 -->
    <dataSource type="POOLED">
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mydatabase"/>
    <property name="username" value="root"/>
    <property name="password" value="password"/>
    </dataSource>
    <!-- 事务管理器配置 -->
    <transactionManager type="JDBC"/>
  • MyBatis 可以很容易地与 Spring 框架集成,通过 Spring 的事务管理来控制数据库事务。
    1
    2
    3
    4
    5
    6
    7
    8
    <!-- Spring 配置 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>
    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>

SpringBoot实体类 —— VO/DTO/PO

  • VO:View Object,主要用于展示层。它的作用是把某个指定前端页面的所有数据封装起来。他的作用主要是减少传输数据量大小和保护数据库隐私数据(如用户密码、用户邮箱等相关信息)不外泄,同时保护数据库的结构不外泄。

  • DTO:Data Transfer Object,数据传输对象,用于展示层与服务层之间的数据传输对象。(注:实际开发中还存在BO,其作用和DTO类似,当业务逻辑不复杂时一般会被合并。)

  • PO:Persistant Object,持久化对象,和数据库形成映射关系。简单说PO就是每一个数据库中的数据表,一个字段对应PO中的一个变量。(也就是我们常用的Entities)

    1、从前端页面中收到JSON格式数据,后端接口中将其封装为一个VO对象;接口接收到VO对象后将其转换为DTO对象,并调用业务类方法对其进行处理;然后处理为PO对象,调用Dao接口连接数据库进行数据访问(查询、插入、更新等)2、后端从数据库得到结果后,根据Dao接口将结果映射为PO对象,然后调用业务类方法将其转换为需要的DTO对象,再根据前端页面实际需求,转换为VO对象进行返回。
  • 类型转换:上述过程中,VO/DTO/PO等实体类中字段常常会存在多数相同,根据业务需求少数不同。为避免频繁的set和get操作对其进行转换,spring为我们提供了多种方法。(1)使用BeanUtils:(springframework包下)(2)使用BeanUtils:(Apache包下)(3)使用modelMapper??

  • DO(Data Object):通常表示数据库中的数据实体,对应数据库表的结构。它主要用于数据存储和数据库操作,包含与数据库表字段一一对应的属性。类中通常包含与数据库表字段对应的成员变量、getter 和 setter 方法。它不应包含业务逻辑,主要负责数据的持久化和映射。
    尽管 PO 和 DO 在一些情况下用法相似,但它们的侧重点有所不同。PO 更侧重于与数据库的交互,强调持久化和数据表映射;而 DO 侧重于在不同层之间传递数据,强调业务逻辑层面的数据封装。

  • BO(Business Object):通常表示业务层的业务实体,主要用于封装业务逻辑。BO 类一般包含与业务逻辑相关的属性和方法,与具体的数据存储形式无关。包含了一些业务逻辑的操作,比如计算、验证等。它不应直接与数据库进行交互,而是通过调用 Service 层或 DAO 层的方法实现数据的获取和存储。


2. Mybatis-Plus

  • 基于MyBatis:MyBatis-Plus是MyBatis的增强工具包,是在MyBatis基础上的扩展。只做增强不做改变,为简化开发、提高效率而生。它提供了更多的便捷、高效的开发功能,简化了开发人员的编码工作,大幅度提高了开发效率。
  • 功能:MyBatis-Plus 集成了MyBatis的核心功能,同时提供了更多针对CRUD操作、条件构造器、分页、代码生成器等功能的封装。
  • 简化操作:可以减少重复的CRUD代码,提供了一些便捷的API接口和工具,使得开发人员能够更方便地进行数据库操作。
  • 引入 MybatisPlus依赖,可以直接代替 Mybatis依赖
    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
    </dependency>
  • MyBatisPlus 的配置项继承了 MyBatis原生配置和一些自己特有的配置。例如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    mybatis-plus:
    type-aliases-package: com.itheima.mp.domain.po # 别名扫描包
    mapper-locations:"classpath*:/mapper/**/*.xml" # Mapper.xml文件地址,默认值
    configuration:
    map-underscore-to-camel-case: true # 是否开户下划线和驼峰的映射
    cache-enabled: false # 是否开户二级缓存
    global-config:
    db-config:
    id-type: assign_id # id为雪花算法生成
    update-strategy: not_null # 更新笑略:只更新非空字段

BaseMapper

  • 定义 Mapper接口并继承 BaseMapper类,泛型指定要与数据库映射的 Java实体类(pojo类);
    MyBatisPlus通过扫描实体类,并基于反射获取实体类信息作为数据库表信息,自动实现 CRUD的逻辑
    • 默认以类名驼峰转下划线作为表名(User类 -> user表)
    • 默认把名为id的字段作为主键
    • 默认把变量名驼峰转下划线作为表的字段名(createTime类属性 -> create_time表字段)
      1
      public interface UserMapper extends BaseMapper<User>{}
  • 如果实体类和表的对应关系不符合 mp的约定,就要自行配置。可以使用注解:
    • @TableName:指定表名称及全局配置
    • @Tableld:指定id字段及相关配置;
      • IdType的常见类型有:AUTO、ASSIGN ID(默认使用,雪花算法)、INPUT
    • @TableField:指定普通字段及相关配置。使用 @TableField的常见场景是:
      • 1、成员变量名与数据库字段名不一致 2、成员变量名以is开头,且是布尔值
        3、成员变量名与数据库关键字冲突 4、成员变量不是数据库字段

条件构造器

MyBatisPlus支持 使用 Wrapper构造各种复杂的where条件,而不需要在 xml中写sql语句。可以满足日常开发的所有需求。

  • QueryWrapper 和LambdaQueryWrapper通常用来构建 select、delete、update的 where条件部分
  • UpdateWrapper 和LambdaUpdateWrapper通常只有在 set语句比较特殊才使用
  • 尽量使用 LambdaQueryWrapper和 LambdaUpdateWrapper避免硬编码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    // 原sql:SELECT id,username,info,balance FROM user WHERE username LIKE ? AND balance >= ?
    void testQuerywrapper() {
    QueryWrapper<User> wrapper = new QueryWrapper<User>() // 1.构建查询条件
    .select("id", "username", "info", "balance")
    .like("username", "o")
    .ge("balance", 1000);
    List<User> users = userMapper.selectList(wrapper); // 2.查询
    }
    // 使用 Lambda替代上方法中的硬编码
    void testLambdaQuerywrapper() {
    LambdaQuerywrapper<User> wrapper = new LambdaQuerywrapper<User>()
    .select(User::getid, User::getUsername, User::getInfo, User::getBalance)
    .like(User::getUsername, "o")
    .ge(User::getBalance, 1000)
    List<User> users = userMapper.selectList(wrapper);
    }
    // 原sql:UPDATE user SET balance = 2000 WHERE (username = "jack")
    void testUpdateByQuerywrapper() {
    User user = new User()// 1.要更新的数据
    user.setBalance(2000);
    QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "jack");// 2.更新的条件
    userMapper.update(user, wrapper);// 3.执行更新
    }
    // 原sql:UPDATE user SET balance = balance - 200 WHERE id in (1,2,4)
    void testUpdatewrapper() {
    List<Long> ids = List.of(1L2L4L);
    Updatewrapper<User> wrapper = new Updatewrapper<User>()
    .setSql("balance = balance - 200")
    .in("id", ids) ;
    userMapper.update(null, wrapper) ;
    }

自定义SQL

我们可以利用 MyBatisPlus的 Wrapper来构建复杂的 Where条件,然后自己定义SQL语句中剩下的部分。
在业务层编写wrapper包含sql中的where部分,在mapper方法声明wrapper变量名称“ew”,最后在mapper对应的xml中自定义sql编写where以外的部分(解决了 不能在业务层编写sql 和 使用mp简化查询语句编写 的矛盾??

IService接口

  • 简单业务方法,直接在controller中调用对应的IService中的方法;
    对于复杂业务,需要在自定义Servicelmpl中编写逻辑,调用对应的BaseMapper中的方法;
    当BaseMapper不足以满足需求时,需要在mapper中编写自定义sql(处理where之外的sql,如update…);
    对于mapper中自定义sql,简单的使用注解编写,复杂的在xml中编写。。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 1、自定义Service接口继承IService接口
    public interface IUserService extends IService<User> {
    }
    // 2、自定义Service实现类,实现自定义接口并继承Servicelmpl类(否则要自己一个个实现IService接口的方法)
    public class UserServiceImpl
    extends ServiceImpl<UserMapper, User> // 泛型中指定mapper和实体类类型
    implements IUserService {
    }
    // 使用方法
    class IUserServiceTest {
    @Autowired
    private IUserService userService;
    @Test
    void testSaveUser() {
    User user = new User();
    user.setUsername(uLiLeiu);
    user.setPassword("123");
    userService.save(user); // 如果方法与BaseMapper中的重复,就不需要BaseMapper了??
    }
    }
  • IService的 Lambda方法:在 自定义的 Servicelmpl类中进行 复杂操作
    1. 需求:复杂查询,查询条件如下(name: 用户名关键字,可以为空;status:用户状态,可以为空;minBalance:最小余额,可以为空;maxBalance:最大余额,可以为空)
      1
      2
      3
      4
      5
      6
      7
      8
      public List<User> queryUsers(String name, Integer status, Integer minBalance Integer maxBalance) {
      return lambdaQuery()
      .like(name != null, User::getUsername, name)
      .eg(status != null, User::getStatus, status)
      .ge(minBalance != null, User::getBalance, minBalance)
      .le(maxBalance != null,User::getBalance, maxBalance)
      .list(); // 如果查询一个记录就是.one
      }
    2. 需求:复杂更新,要求如下(按id更新,更新为扣后余额,如果扣减后余额为0,则将用户status修改为冻结状态(2))
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      @Transaction
      public List<User> deductBalance(Long id,Integer money) {
      ...
      // 4.扣城余额 update tb_user set balance = balance - ?
      int remainBalance = user.getBalance() - money;
      lambdaUpdate()
      .set(User::getBalance, remainBalance)
      .set(remainBalance == 0, User::getStatus, 2) // 如果余额0,修改状态
      .eq(User::getId, id) // 相当于 where
      .eq(User::getBalance,user.getBalance()) // 乐观锁
      .update(); // 更新
      }
  • IService批量新增(批处理):开启 rewriteBatchedStatements=true参数

分页插件

首先,要在配置类中注册MyBatisplus的核心插件,同时添加分页插件
接着,就可以使用分页的API了

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class MybatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() { // 拦截器的形式实现插件
// 1. 初始化核心插件
MybatisplusInterceptor interceptor = new MybatisplusInterceptor();
// 2. 添加分页插件
PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor(DbType.MYSOL);
pageInterceptor.setMaxLimit(100L); // 设置分页上限
interceptor.addInnerInterceptor(pageInterceptor); // 添加到核心拦截器
return interceptor;
}
}
1
2
3
4
5
6
7
8
void testPageQuery() {
int pageNo = 1, pageSize = 5; // 分页参数
Page<User> page = Page.of(pageNo,pageSize);
page.addOrder(new OrderItem("balance"false)); // 排序参数,通过OrderItem来指定
// 分页查询
Page<User> p = userService.page(page);
// 总条数 p.getTotal(); 总页数 p.getPages(); 分页数据 List<User> records = p.getRecords();
}