spring boot 2.1学习笔记【六】SpringBoot 2集成MySQL,Spring Data JPA

springboot系列学习笔记全部文章请移步值博主专栏**: spring boot 2.X/spring cloud Greenwich。
由于是一系列文章,所以后面的文章可能会使用到前面文章的项目。springboot系列代码全部上传至GitHub:https://github.com/liubenlong/springboot2_demo
本系列环境:Java11;springboot 2.1.1.RELEASE;springcloud Greenwich.RELEASE;MySQL 8.0.5;

文章目录
使用spring data jpa
使用JpaRepository默认方法
使用自定义的方法
使用@Query来指定本地查询
事物
多数据源事务
遇到的异常
time zone 时区错误
mysql-connector-java 8.X 版本的驱动的SSL问题
配置项说明
使用spring data jpa
需要添加以下依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.13</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

最简化的配置(为了打印SQL这里配置了show-sql):

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/test
    username: ***
    password: ***
  jpa:
    show-sql: true

创建测试的数据库

CREATE TABLE `stu` (
   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
   `name` varchar(45) DEFAULT NULL,
   `age` int(11) DEFAULT NULL,
   `address` varchar(45) DEFAULT NULL,
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

随便添加一条记录。
编写Stu的实体类:

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.io.Serializable;

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Stu implements Serializable {
    @Id
    /*
    这里是hibernate的主键生成策略。oracle中需要 设置sequence,MySQL中则指定identity使用自增字段。具体请参考hibernate的配置
     */
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(nullable = false)
    private String name;
    @Column
    private Integer age;
    @Column
    private String address;
}

编写dao层。JpaRepository是springdata提供的的数据库访问层的工具类,可以大大简化代码的编写。JpaRepository提供了以下方法,可以满足大部分的需求:
在这里插入图片描述

import com.example.pojo.Stu;
import org.springframework.data.jpa.repository.JpaRepository;

public interface StuRepository extends JpaRepository<Stu, Long> {

}

使用JpaRepository默认方法

为了简单,我没有写service;

import com.example.dao.StuRepository;
import com.example.pojo.Stu;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.List;

@RestController
@Slf4j
public class StuController {
    @Autowired
    private StuRepository stuRepository;

    @GetMapping("/findAll")
    public List<Stu> findAll() {
        List<Stu> stus = stuRepository.findAll();

        return stus;
    }

    /**
     * 创建
     *
     * @return
     */
    @GetMapping("/createOne")
    public List<Stu> createOne() {
        Stu stu = Stu.builder().name("李四").age(12).address("西湖").build();
        Stu save = stuRepository.save(stu);

        log.info(save.toString());

        List<Stu> stus = Arrays.asList(Stu.builder().name("李四").age(12).address("西湖").build(),
                Stu.builder().name("王五").age(12).address("南京路").build(),
                Stu.builder().name("交易员韩国").age(12).address("二环").build());
        List<Stu> stus1 = stuRepository.saveAll(stus);

        return stus1;
    }

    @GetMapping("/deleteById")
    public void deleteById() {
        stuRepository.existsById(1L);//判断是否存在
        stuRepository.deleteById(1L);//删除
    }


    /**
     * 根据条件查询
     *
     * @return
     */
    @GetMapping("/findByExample")
    public List<Stu> findByExample() {
        Stu stu = Stu.builder().age(12).build();
        Example<Stu> example = Example.of(stu);

        List<Stu> all = stuRepository.findAll(example);
        return all;
    }

    /**
     * 实现分页查询
     *
     * @return
     */
    @GetMapping("/findWithPage")
    public Page<Stu> findWithPage() {
        Stu stu = Stu.builder().age(12).build();
        Example<Stu> example = Example.of(stu);
        Page<Stu> all = stuRepository.findAll(example, PageRequest.of(1, 5, Sort.by("name")));

        return all;
    }
}

启动服务, 可以看到默认是使用Hibernate:


另外需要说明的是,1.5.X版本springboot连接池默认是使用的Tomcat连接池,2.X版本默认使用hikari连接池。具体参见另一篇文章springboot 中数据源配置,连接池配置,源码剖析,如何选择连接池。hikari连接池后续再讲

接口访问结果:


更加详细的JpaRepository用法请自行搜索。

使用自定义的方法
自定义的简单查询就是根据方法名来自动生成SQL,主要的语法是findXXBy,readAXXBy,queryXXBy,countXXBy, getXXBy后面跟属性名称。

按照Spring Data的规范,查询方法以find | read | get 开头,涉及查询条件时,条件的属性用条件关键字连接,
要注意的是:条件属性以首字母大写。

 

实例:我们在StuRepository中添加一个方法:

public interface StuRepository extends JpaRepository<Stu, Long> {
    /**
     * 根据名字自定义方法
     * @param name
     * @param age
     * @return
     */
    List<Stu> findByNameAndAge(String name, int age);
}

编写测试方法:

/**
     * 使用自定义的查询方法
     * 其实上面的JpaRepository默认的方法已经够用了,这个可不使用。经常用到的方法可以写,减少代码量
     * @return
     */
    @GetMapping("/findByNameAndAge")
    public List<Stu> findByNameAndAge() {
        List<Stu> stus = stuRepository.findByNameAndAge("李四", 12);
        return stus;
    }

使用@Query来指定本地查询

有时候可能需要自己编写SQL,那么可以使用@Query来编写自己的SQL语句
实例:

public interface StuRepository extends JpaRepository<Stu, Long> {
    @Query(value="select * from stu where name like %?%" ,nativeQuery=true)
    List<Stu> findLikeName(String name);
}

暂不支持翻页及动态排序等功能。除非使用JpaRepository不能满足需求,否则一般不会自己来编写SQL。

事物
spring提供了声明式的事务管理@Transactional。使用起来非常方便。这里只展示如何使用,具体原理后续再讲。

由于spring的事务管理是通过AOP动态代理实现的,所以调用方与事务方法不得在同一个方法内。

这里我们新建一个StuServer:

@Component
@Slf4j
public class StuService {

    @Autowired
    private StuRepository stuRepository;

    @Transactional(rollbackFor = Exception.class)
    public void createStu() {
        Stu stu = Stu.builder().age(100).address("1111").name("周星星").build();
        Stu save = stuRepository.save(stu);
        log.info(save.toString());
        int a = 1/0;

        stu = Stu.builder().age(200).address("2222").name("周星星2").build();
        Stu save1 = stuRepository.save(stu);
        log.info(save1.toString());
    }
}

在controller中测试

/**
 *事务测试
 * @return
 */
@GetMapping("/testTransactional")
public void testTransactional() {
    stuService.createStu();
}

注意:这里rollbackFor最好写上,否则可能会存在一些运行时异常不活不到哦。

多数据源事务
请参考笔者另一篇文章: spring boot多数据源配置,多数据源事务。只需要注意每个事务方法都明确指定事务管理器就行了(如果不加事务管理器,默认是第一个加载的数据源的事务)。

遇到的异常
time zone 时区错误
The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more than one time zone. You must configure either the
server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize
time zone support.
1
2
3
解决办法:
使用root用户登录mysql,执行命令show variables like '%time_zone%';

发现时区是系统默认的,而默认是美国时区,而我们中国要比他们迟8小时。所以需要对其进行修改:
set global time_zone='+8:00';
执行完成之后,再次查询

如果没有变为+8:00,退出客户端,重新登录查询即可。

另外一种解决办法在连接URL上指定serverTimezone):
jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8

mysql-connector-java 8.X 版本的驱动的SSL问题
我们这里手动禁用ssl,否则会报错javax.net.ssl.SSLException: closing inbound before receiving peer's close_notify

如何使用SSL后续再测试。

参考:http://www.360doc.com/content/18/0725/10/15874231_773062498.shtml

配置项说明

# DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.continue-on-error=false # Whether to stop if an error occurs while initializing the database.
spring.datasource.data= # Data (DML) script resource references.
spring.datasource.data-username= # Username of the database to execute DML scripts (if different).
spring.datasource.data-password= # Password of the database to execute DML scripts (if different).
spring.datasource.dbcp2.*= # Commons DBCP2 specific settings
spring.datasource.driver-class-name= # Fully qualified name of the JDBC driver. Auto-detected based on the URL by default.
spring.datasource.generate-unique-name=false # Whether to generate a random datasource name.
spring.datasource.hikari.*= # Hikari specific settings
spring.datasource.initialization-mode=embedded # Initialize the datasource with available DDL and DML scripts.
spring.datasource.jmx-enabled=false # Whether to enable JMX support (if provided by the underlying pool).
spring.datasource.jndi-name= # JNDI location of the datasource. Class, url, username & password are ignored when set.
spring.datasource.name= # Name of the datasource. Default to "testdb" when using an embedded database.
spring.datasource.password= # Login password of the database.
spring.datasource.platform=all # Platform to use in the DDL or DML scripts (such as schema-${platform}.sql or data-${platform}.sql).
spring.datasource.schema= # Schema (DDL) script resource references.
spring.datasource.schema-username= # Username of the database to execute DDL scripts (if different).
spring.datasource.schema-password= # Password of the database to execute DDL scripts (if different).
spring.datasource.separator=; # Statement separator in SQL initialization scripts.
spring.datasource.sql-script-encoding= # SQL scripts encoding.
spring.datasource.tomcat.*= # Tomcat datasource specific settings
spring.datasource.type= # Fully qualified name of the connection pool implementation to use. By default, it is auto-detected from the classpath.
spring.datasource.url= # JDBC URL of the database.
spring.datasource.username= # Login username of the database.
spring.datasource.xa.data-source-class-name= # XA datasource fully qualified name.
spring.datasource.xa.properties= # Properties to pass to the XA data source.


# JPA (JpaBaseConfiguration, HibernateJpaAutoConfiguration)
spring.data.jpa.repositories.bootstrap-mode=default # Bootstrap mode for JPA repositories.
spring.data.jpa.repositories.enabled=true # Whether to enable JPA repositories.
spring.jpa.database= # Target database to operate on, auto-detected by default. Can be alternatively set using the "databasePlatform" property.
spring.jpa.database-platform= # Name of the target database to operate on, auto-detected by default. Can be alternatively set using the "Database" enum.
spring.jpa.generate-ddl=false # Whether to initialize the schema on startup.
spring.jpa.hibernate.ddl-auto= # DDL mode. This is actually a shortcut for the "hibernate.hbm2ddl.auto" property. Defaults to "create-drop" when using an embedded database and no schema manager was detected. Otherwise, defaults to "none".
spring.jpa.hibernate.naming.implicit-strategy= # Fully qualified name of the implicit naming strategy.
spring.jpa.hibernate.naming.physical-strategy= # Fully qualified name of the physical naming strategy.
spring.jpa.hibernate.use-new-id-generator-mappings= # Whether to use Hibernate's newer IdentifierGenerator for AUTO, TABLE and SEQUENCE.
spring.jpa.mapping-resources= # Mapping resources (equivalent to "mapping-file" entries in persistence.xml).
spring.jpa.open-in-view=true # Register OpenEntityManagerInViewInterceptor. Binds a JPA EntityManager to the thread for the entire processing of the request.
spring.jpa.properties.*= # Additional native properties to set on the JPA provider.
spring.jpa.show-sql=false # Whether to enable logging of SQL statements.

springboot系列学习笔记全部文章请移步值博主专栏**: spring boot 2.X/spring cloud Greenwich。
由于是一系列文章,所以后面的文章可能会使用到前面文章的项目。springboot系列代码全部上传至GitHub:https://github.com/liubenlong/springboot2_demo
本系列环境:Java11;springboot 2.1.1.RELEASE;springcloud Greenwich.RELEASE;MySQL 8.0.5;

posted @ 2019-03-19 22:57  门罗的魔术师  阅读(768)  评论(0编辑  收藏  举报