Spring Boot集成SQL数据库2

Spring Boot 集成 MyBatis操作MySQL

MyBatis是一款半自动的ORM框架。由于某国内大厂的广泛使用,MyBatis在国内非常火热(在国外其热度不如Hibernate)。

首先还是集成依赖:

implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.0'
implementation 'mysql:mysql-connector-java:8.0.20'

套路与jdbc类似,但starter并不是官方的了,而是mybatis自己做的starter,感兴趣的可以来这里看下具体组成(会有惊喜)。

接下来是yaml配置环节:

spring.datasource:
  url: jdbc:mysql://127.0.0.1:3306/homs_demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false
  username: HomsDemo
  password: 123456
  hikari:
    minimumIdle: 10
    maximumPoolSize: 100

# mybatis extra
mybatis:
  configuration:
    map-underscore-to-camel-case: true
  type-aliases-package: com.coder4.homs.demo.server.mybatis.dataobject

不难发现,数据库链接的定义复用了jdbc的那一套,MyBatis的定义分3行,如下:

  • configuration:开启驼峰规则转化

  • type-aliases-package:mapper文件存放的包名

更多MyBatis的配置选项可以参考[这里](mybatis-spring-boot-autoconfigure – Introduction)

接着,我们定义Mapper,在MyBatis中,Mapper相当于前面手写的Repository,定义如下:

package com.coder4.homs.demo.server.mybatis.mapper;

import com.coder4.homs.demo.server.mybatis.dataobject.UserDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @author author
 * @since 2021-09-09
 */
@Repository
@Mapper
public interface UserMapper {

    @Insert("INSERT INTO users(name) VALUES(#{name})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    long create(UserDO user);

    @Select("SELECT * FROM users WHERE id = #{id}")
    UserDO getUser(@Param("id") Long id);

    @Select("SELECT * FROM users WHERE name = #{name}")
    UserDO getUserByName(@Param("name") String name);

}

你可能会奇怪:这不是接口(interface)么,并没有实现?

是的,通过定义@Repository和@Mapper,MyBatis会通过运行时的切面注入,帮我们自动实现,具体执行的SQL和映射,会读取@Select、@Options等注解中的配置。

经过上述介绍,你可以发现:

MyBatis可以直接通过注解的方式快速访问数据库,(相对于JDBC的)精简了大量无用代码。

同时,MyBatis依然需要指定运行的SQL语句,这与JDBC的方式是一致的。虽然有些繁琐,但可以保证性能可控。

如果你在网上搜索"MyBatis Spring集成",会找到大量xml配置的用法。

在一些老项目中,xml是标准的集成方式。在这种配置方式下,配置繁琐、代码量大,即使借助"MyBatisX"等插件,也依然较为复杂。

因此,除非你要维护遗留的老项目代码,我都建议你使用(本文中)注解式集成MyBatis。

Spring Boot集成 JPA 操作MySQL

JPA的全称是Java Persistence API,即持久化访问规范API。

Spring也提供了集成JPA的方案,称为 Spring Data JPA,其底层是通过Hibernate的JPA来实现的。

首先集成依赖:

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'mysql:mysql-connector-java:8.0.20'

与前面类似,不再重复介绍。

接着是配置:

# jdbc demo
spring.datasource:
  url: jdbc:mysql://127.0.0.1:3306/homs_demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false
  username: HomsDemo
  password: 123456
  hikari:
    minimumIdle: 10
    maximumPoolSize: 100


# jpa demo
spring.jpa:
  database-platform: org.hibernate.dialect.MySQL8Dialect
  hibernate.ddl-auto: validate

在MySQL连接上,我们依然复用了Spring DataSource的配置。

jpa侧的配置为:

  • database-platform:设置使用MySQL8语法

  • hibernate.ddl-auto:只校验表,不回主动更新数据表的结构

接着,我们来定义实体(Entity):

@Entity
@Data
@Table(name = "users")
public class UserEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    // @Column(name = "name")
    private String name;

    public User toUser() {
        User user = new User();
        user.setId(id);
        user.setName(name);
        return user;
    }

}

这里我们将UserEntity与表"users"做了关联。

接下来是Repository:

@Repository
public interface UserJPARepository extends CrudRepository<UserEntity, Long> {

    Collection<UserEntity> findByName(String name);

}

我们继承了CrudRepository,他会自动生成针对UserEntity的CRUD操作。

此外,我们还定义了1个额外函数:

  • findByName,通过隐士语法规则,让JPA自动帮我们生成对应SQL

从直观感受上,JPA比MyBatis更加“高级” -- 一些简单的SQL都不用写了。

但天下真的有免费的馅饼么?我们先卖个关子。

JMJ应该选哪个

经过这两节的介绍,你已经掌握了JDBC、MyBatis、JPA三种操作数据库的方式。

在实战中,究竟要选哪个呢?

从易用性的角度来评估,我们可以得出结论:JPA > MyBatis > JDBC

那么从性能的角度来看呢?

我们使用wrk做了(get-by-id接口的)简单压测,结论如下:

读QPS
JDBC457
MyBatis445
JPA114

这里,你会惊讶的发现:

  • JDBC和MyBatis的性能差别不大,在5%以内

  • JPA(Hibernate)的性能,居然只有其余两种方式的1/3

如此差的性能,真的让人百思不得其解,我尝试打印了SQL和执行耗时,并没有发现什么异常。

更进一步的,我们尝试用指定SQL的方式,替换了自动生成的接口,如下

@Repository
public interface UserJPARepository extends CrudRepository<UserEntity, Long> {

    @Query(value = "SELECT * FROM users WHERE id = :id", nativeQuery = true)
    Optional<UserEntity> findByIdFast(@Param("id") long id);

}

这次的压测结果是:447,性能基本和JDBC持平了。但是这种NativeSQL的用法并没有使用自动生成SQL的功能,没有发挥Hibernate本来的功效。

所以,我们认为,锅在于Hibernate自动生成SQL的逻辑耗时过大。

当然,Hibernate也不是一无是处,针对多层关联,建模复杂的场景,使用Entity做映射,会更加方便。

让我们回到前面的问题上:JMJ应该选哪个?

  • 如果对性能有极致要求,建议JDBC或者MyBatis。

  • 如果建模场景复杂,嵌套密集,且对性能要求不高,可以选用Hibernate。