SpringDataJAP使用详解

SpringDataJPA笔记


第一章 为什么要学习jpa?
  • 第一,大势所趋,大厂必备技能。

  • 第二,提升开发效率。

  • 第三,提高技术水平。

  • 第四,求职加分项。


第二章 初识jpa
快速启动一个springboot项目集成springDataJPA。

1.1 引入依赖

       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
            <version>2.5.0</version>
        </dependency>

1.2 创建实体类

 @Data
 @Entity
 public class User {
     /**
      * This field was generated by MyBatis Generator.
      * This field corresponds to the database column user.id
      *
      * @mbg.generated Sun May 23 12:25:53 CST 2021
      */
     @TableId(type = IdType.AUTO)
     @Id
     @GeneratedValue(strategy= GenerationType.AUTO)
     private Integer id;
 
     /**
      * This field was generated by MyBatis Generator.
      * This field corresponds to the database column user.username
      *
      * @mbg.generated Sun May 23 12:25:53 CST 2021
      */
     private String username;
 
     /**
      * This field was generated by MyBatis Generator.
      * This field corresponds to the database column user.password
      *
      * @mbg.generated Sun May 23 12:25:53 CST 2021
      */
     private String password;
 
     /**
      * This field was generated by MyBatis Generator.
      * This field corresponds to the database column user.salt
      *
      * @mbg.generated Sun May 23 12:25:53 CST 2021
      */
     private String salt;
 }

​ 1.3 创建UserRepository

         ~~~java
         public interface UserRepository extends JpaRepository<User, Integer> {
         }
         
         ~~~

  1. Spring Data Common

    2.1 是什么?

    ​ 它定义了一套操作数据源的接口。CrudRepository、PageingAndSortingRepository、JpaRepository…


    2.2 为什么能操作数据库?

    ​ Resposiory 是 Spring Data 里面进行数据库操作顶级的抽象接口,里面什么方法都没有,但是如果任何接口继承它,就能得到一个 Repository,还可以实现 JPA 的一些默认实现方法。Spring 利用 Respository 作为 DAO 操作的 Type,以及利用 Java 动态代理机制就可以实现很多功能,比如为什么接口就能实现 DB 的相关操作?这就是 Spring 框架的高明之处。

    ​ Spring 在做动态代理的时候,只要是它的子类或者实现类,再利用 T 类以及 T 类的 主键 ID 类型作为泛型的类型参数,就可以来标记出来、并捕获到要使用的实体类型,就能帮助使用者进行数据库操作。


    2.3 Repository关系层次?

    在这里插入图片描述


2.4 7 个大 Repository 接口:

  • Repository(org.springframework.data.repository),没有暴露任何方法;
  • CrudRepository(org.springframework.data.repository),简单的 Curd 方法;
  • PagingAndSortingRepository(org.springframework.data.repository),带分页和排序的方法;
  • QueryByExampleExecutor(org.springframework.data.repository.query),简单 Example 查询;
  • JpaRepository(org.springframework.data.jpa.repository),JPA 的扩展方法;
  • JpaSpecificationExecutor(org.springframework.data.jpa.repository),JpaSpecification 扩展查询;
  • QueryDslPredicateExecutor(org.springframework.data.querydsl),QueryDsl 的封装。

  1. Defining Query Methods 的命名语法与参数

​ 根据方法名来定义查询条件。

    /**
     * 根据用户名来查询
     *
     * @param userName
     * @return
     */
    List<User> findByUsername(String userName);

3.1 Defining Query Method(DQM)语法

img

3.2 分页查询 排序查询

~~~java
//查询user里面的lastname=jk的第一页,每页大小是20条;并会返回一共有多少页的信息

Page<User> users = userRepository.findByLastname("jk",PageRequest.of(1, 20));

//查询user里面的lastname=jk的第一页的20条数据,不知道一共多少条

Slice<User> users = userRepository.findByLastname("jk",PageRequest.of(1, 20));

//查询出来所有的user里面的lastname=jk的User数据,并按照name正序返回List

List<User> users = userRepository.findByLastname("jk",new Sort(Sort.Direction.ASC, "name"))

//按照createdAt倒序,查询前一百条User数据

List<User> users = userRepository.findByLastname("jk",PageRequest.of(0, 100, Sort.Direction.DESC, "createdAt"));

~~~

具体调用:

//查询user里面的lastname=jk的第一页,每页大小是20条;并会返回一共有多少页的信息

Page<User> users = userRepository.findByLastname("jk",PageRequest.of(1, 20));

//查询user里面的lastname=jk的第一页的20条数据,不知道一共多少条

Slice<User> users = userRepository.findByLastname("jk",PageRequest.of(1, 20));

//查询出来所有的user里面的lastname=jk的User数据,并按照name正序返回List

List<User> users = userRepository.findByLastname("jk",Sort.by(Sort.Direction.ASC, "name"))

//按照createdAt倒序,查询前一百条User数据

List<User> users = userRepository.findByLastname("jk",PageRequest.of(0, 100, Sort.Direction.DESC, "createdAt"));


  1. 如何利用 Repository 中的方法返回值解决实际问题?
    Drawing 5.png
    1. @Query语法

​ 5.1 简单示例

                 ~~~java
                     /**
                      * 测试@Query方法
                      *
                      * @param username
                      * @return
                      */
                     @Query("from User where username=:username")
                     List<User> queryUserFromUsername(@Param("username") String username);
                 ~~~

​ 5.2 @Query里边的方法

package org.springframework.data.jpa.repository;

public @interface Query {

   /**

    * 指定JPQL的查询语句。(nativeQuery=true的时候,是原生的Sql语句)

	  */

   String value() default "";

   /**

	  * 指定count的JPQL语句,如果不指定将根据query自动生成。

    * (如果当nativeQuery=true的时候,指的是原生的Sql语句)

    */

   String countQuery() default "";

   /**

    * 根据哪个字段来count,一般默认即可。

	*/

   String countProjection() default "";

   /**

    * 默认是false,表示value里面是不是原生的sql语句

	*/

   boolean nativeQuery() default false;

   /**

    * 可以指定一个query的名字,必须唯一的。

	  * 如果不指定,默认的生成规则是:

    * {$domainClass}.${queryMethodName}

    */

   String name() default "";

   /*

    * 可以指定一个count的query的名字,必须唯一的。

	  * 如果不指定,默认的生成规则是:

    * {$domainClass}.${queryMethodName}.count

    */

   String countName() default "";

}

5.3 动态sql

img
    /**
     * 测试@Query多条件查询
     *
     * @param user
     * @return
     */
    @Query("select u  from User u where (u.username is null or u.username = '' or u.username = :#{#user.username}) and (u.password is null or u.password = :#{#user.password})")
    List<User> queryManyWrapper(@Param("user") User user);
SELECT
	user0_.id AS id1_0_,
	user0_.PASSWORD AS password2_0_,
	user0_.salt AS salt3_0_,
	user0_.username AS username4_0_ 
FROM
	USER user0_ 
WHERE
	(
		user0_.username IS NULL 
		OR user0_.username = '' 
	OR user0_.username =?) 
	AND (
	user0_.PASSWORD IS NULL 
	OR user0_.PASSWORD =?)
  1. 实体类里的注解有哪些?

​ 6.1 字段注解

​ 注解要么全部加载属性上,要么全部加载get/set方法上。

@Column(length = 20, nullable = false)

public String getUserName(){

    return userName;

}

@Column(length = 20, nullable = false)

private String userName;


6.2 注意事项

  • 只要是在 @Entity 的实体里面被注解标注的字段,都会被映射到数据库中,除了使用 @Transient 注解的字段之外。

  • 实体里面必须要有一个主键,主键标示的字段可以是单个字段,也可以是复合主键字段。

6.3 主键注解

public enum GenerationType {

    //通过表产生主键,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植。

    TABLE,

    //通过序列产生主键,通过 @SequenceGenerator 注解指定序列名, MySql 不支持这种方式;

    SEQUENCE,

    //采用数据库ID自增长, 一般用于mysql数据库

    IDENTITY,

    //JPA 自动选择合适的策略,是默认选项;

    AUTO

}

6.4常用注解

  • @Entity 用于定义对象将会成为被 JPA 管理的实体,必填,将字段映射到指定的数据库表中,使用起来很简单,直接用在实体类上面即可,通过源码表达的语法如下:
  • @Table 用于指定数据库的表名,表示此实体对应的数据库里面的表名,非必填,默认表名和 entity 名字一样。
  • @Access 用于指定 entity 里面的注解是写在字段上面,还是 get/set 方法上面生效,非必填。在默认不填写的情况下,当实体里面的第一个注解出现在字段上或者 get/set 方法上面,就以第一次出现的方式为准。
  • @Id 定义属性为数据库的主键,一个实体里面必须有一个主键,但不一定是这个注解,可以和 @GeneratedValue 配合使用或成对出现。
  • @Enumerated 这个注解很好用,因为它对 enum 提供了下标和 name 两种方式,用法直接映射在 enum 枚举类型的字段上。请看下面源码。
  • @Basic 表示属性是到数据库表的字段的映射。如果实体的字段上没有任何注解,默认即为 @Basic。也就是说默认所有的字段肯定是和数据库进行映射的,并且默认为 Eager 类型。
  • @Transient 表示该属性并非一个到数据库表的字段的映射,表示非持久化属性。JPA 映射数据库的时候忽略它,与 @Basic 有相反的作用。也就是每个字段上面 @Transient 和 @Basic 必须二选一,而什么都不指定的话,默认是 @Basic。
  • @Column 定义该属性对应数据库中的列名。
public @interface Column {

    //数据库中的表的列名;可选,如果不填写认为字段名和实体属性名一样。

    String name() default "";

    //是否唯一。默认flase,可选。

    boolean unique() default false;

    //数据字段是否允许空。可选,默认true。

    boolean nullable() default true;

    //执行insert操作的时候是否包含此字段,默认,true,可选。

    boolean insertable() default true;

    //执行update的时候是否包含此字段,默认,true,可选。

    boolean updatable() default true;

    //表示该字段在数据库中的实际类型。

    String columnDefinition() default "";

   //数据库字段的长度,可选,默认255

    int length() default 255;

}

  • @Temporal 用来设置 Date 类型的属性映射到对应精度的字段,存在以下三种情况:
    • @Temporal(TemporalType.DATE)映射为日期 // date (只有日期
    • @Temporal(TemporalType.TIME)映射为日期 // time (只有时间
    • @Temporal(TemporalType.TIMESTAMP)映射为日期 // date time (日期+时间

6.5. 联合主键

  • 如何通过@IdClass做联合主键?

    ~~~java
    package com.example.jpa.example1;
    
    import lombok.AllArgsConstructor;
    
    import lombok.Builder;
    
    import lombok.Data;
    
    import lombok.NoArgsConstructor;
    
    import java.io.Serializable;
    
    @Data
    
    @Builder
    
    @AllArgsConstructor
    
    @NoArgsConstructor
    
    public class UserInfoID implements Serializable {
    
       private String name,telephone;
    
    }
    
    ~~~
    
@Entity

@Data

@Builder

@IdClass(UserInfoID.class)

@AllArgsConstructor

@NoArgsConstructor

public class UserInfo {

   private Integer ages;

   @Id

   private String name;

   @Id

   private String telephone;

}

  • Embeddable 与 @EmbeddedId 注解使用
@Data

@Builder

@AllArgsConstructor

@NoArgsConstructor

@Embeddable

public class UserInfoID implements Serializable {

   private String name,telephone;

}

@Entity

@Data

@Builder

@AllArgsConstructor

@NoArgsConstructor

public class UserInfo {

   private Integer ages;

   @EmbeddedId

   private UserInfoID userInfoID;

   @Column(unique = true)

   private String uniqueNumber;

}

6.6 实体间的继承关系如何处理

​ 关系可以分为以下几类:

  • 纯粹的继承,和表没关系,对象之间的字段共享。利用注解 @MappedSuperclass,协议规定父类不能是 @Entity。

  • 单表多态问题,同一张 Table,表示了不同的对象,通过一个字段来进行区分。利用@Inheritance(strategy = InheritanceType.SINGLE_TABLE)注解完成,只有父类有 @Table。

    package com.example.jpa.example1.book;
    
    import lombok.Data;
    
    import javax.persistence.*;
    
    @Entity(name="book")
    
    @Data
    
    @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
    
    @DiscriminatorColumn(name="color", discriminatorType = DiscriminatorType.STRING)
    
    public class Book {
    
       @Id
    
       @GeneratedValue(strategy= GenerationType.AUTO)
    
       private Long id;
    
       private String title;
    
    }
    
    
    package com.example.jpa.example1.book;
    
    import lombok.Data;
    
    import lombok.EqualsAndHashCode;
    
    import javax.persistence.DiscriminatorValue;
    
    import javax.persistence.Entity;
    
    @Entity
    
    @Data
    
    @EqualsAndHashCode(callSuper=false)
    
    @DiscriminatorValue("blue")
    
    public class BlueBook extends Book{
    
       private String blueMark;
    
    }
    
    
    //红皮书
    
    @Entity
    
    @DiscriminatorValue("red")
    
    @Data
    
    @EqualsAndHashCode(callSuper=false)
    
    public class RedBook extends Book {
    
       private String redMark;
    
    }
    
    
  • 多表多态,每一个子类一张表,父类的表拥有所有公用字段。通过@Inheritance(strategy = InheritanceType.JOINED)注解完成,父类和子类都是表,有公用的字段在父表里面。

  • Object 的继承,数据库里面每一张表是分开的,相互独立不受影响。通过@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)注解完成,父类(可以是一张表,也可以不是)和子类都是表,相互之间没有关系。


  1. @Entity 之间的关联关系注解如何正确使用?

​ 实体与实体之间的关联关系一共分为四种,分别为 OneToOne、OneToMany、ManyToOne 和 ManyToMany;而实体之间的关联关系又分为双向的和单向的。

  • oneToOne关联关系
public @interface OneToOne {

    //表示关系目标实体,默认该注解标识的返回值的类型的类。

    Class targetEntity() default void.class;

    //cascade 级联操作策略,就是我们常说的级联操作

    CascadeType[] cascade() default {};

    //数据获取方式EAGER(立即加载)/LAZY(延迟加载)

    FetchType fetch() default EAGER;

    //是否允许为空,默认是可选的,也就表示可以为空;

    boolean optional() default true;

    //关联关系被谁维护的一方对象里面的属性名字。 双向关联的时候必填

    String mappedBy() default "";

    //当被标识的字段发生删除或者置空操作之后,是否同步到关联关系的一方,即进行通过删除操作,默认flase,注意与        CascadeType.REMOVE 级联删除的区别

    boolean orphanRemoval() default false;

}

  • mappedBy注意事项

只有关联关系的维护方才能操作两个实体之间外键的关系。被维护方即使设置了维护方属性进行存储也不会更新外键关联。

mappedBy 不能与 @JoinColumn 或者 @JoinTable 同时使用,因为没有意义,关联关系不在这里面维护。

==此外,mappedBy 的值是指另一方的实体里面属性的字段,而不是数据库字段,也不是实体的对象的名字。==也就是维护关联关系的一方属性字段名称,或者加了 @JoinColumn / @JoinTable 注解的属性字段名称。如上面的 User 例子 user 里面 mappedBy 的值,就是 UserInfo 里面的 user 字段的名字。

  • 在 CascadeType 的用法中,CascadeType 的枚举值只有五个

    • CascadeType.PERSIST 级联新建
    • CascadeType.REMOVE 级联删除
    • CascadeType.REFRESH 级联刷新
    • CascadeType.MERGE 级联更新
    • CascadeType.ALL 四项全选
  • orphanRemoval 属性用法

​ orphanRemoval 表示当关联关系被删除的时候,是否应用级联删除,默认 false。。

  • @JoinCloumns & JoinColumn
public @interface JoinColumn {

    //关键的字段名,默认注解上的字段名,在@OneToOne代表本表的外键字段名字;

    String name() default "";

    //与name相反关联对象的字段,默认主键字段

    String referencedColumnName() default "";

    //外键字段是否唯一

    boolean unique() default false;

    //外键字段是否允许为空

    boolean nullable() default true;

    //是否跟随一起新增

    boolean insertable() default true;

    //是否跟随一起更新

    boolean updatable() default true;

    //JPA2.1新增,外键策略

    ForeignKey foreignKey() default @ForeignKey(PROVIDER_DEFAULT);

}

  • ManyToOne& @OneToMany
public @interface ManyToOne {

    Class targetEntity() default void.class;

    CascadeType[] cascade() default {};

    FetchType fetch() default EAGER;

    boolean optional() default true;

}

 public @interface OneToMany {

    Class targetEntity() default void.class;

 //cascade 级联操作策略:(CascadeType.PERSIST、CascadeType.REMOVE、CascadeType.REFRESH、CascadeType.MERGE、CascadeType.ALL)

如果不填,默认关系表不会产生任何影响。

    CascadeType[] cascade() default {};

//数据获取方式EAGER(立即加载)/LAZY(延迟加载)

    FetchType fetch() default LAZY;

    //关系被谁维护,单项的。注意:只有关系维护方才能操作两者的关系。

    String mappedBy() default "";

//是否级联删除。和CascadeType.REMOVE的效果一样。两种配置了一个就会自动级联删除

    boolean orphanRemoval() default false;

}

  1. jaskson注解
Lark20201009-105051.png
第三章 高级用法和实战
  • JpaSpecificationExecutor
    • JpaSpecificationExecutor 的基本语法
public interface QueryByExampleExecutor<T> { 

 //根据“实体”查询条件,查找一个对象

<S extends T> S findOne(Example<S> example);

//根据“实体”查询条件,查找一批对象

<S extends T> Iterable<S> findAll(Example<S> example); 

//根据“实体”查询条件,查找一批对象,可以指定排序参数

<S extends T> Iterable<S> findAll(Example<S> example, Sort sort);

 //根据“实体”查询条件,查找一批对象,可以指定排序和分页参数 

<S extends T> Page<S> findAll(Example<S> example, Pageable pageable);

//根据“实体”查询条件,查找返回符合条件的对象个数

<S extends T> long count(Example<S> example); 

//根据“实体”查询条件,判断是否有符合条件的对象

<S extends T> boolean exists(Example<S> example); 

}

  • 基本使用
 @Test
    public void testSpeQuery() throws JsonProcessingException {
        User user = User.builder()
                .username("wuhaiqing").gender(Gender.MAN)
                .build();
        //        System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(address));
        // 可以打印出来看看参数是什么
        //创建匹配器,即如何使用查询条件
        ExampleMatcher exampleMatcher = ExampleMatcher
                .matching()
                .withIgnoreNullValues()
                .withIgnoreCase()
                .withMatcher("username", ExampleMatcher.GenericPropertyMatchers.startsWith())
                .withMatcher("gender",ExampleMatcher.GenericPropertyMatchers.startsWith());
        Page<User> u = userRepository.findAll(Example.of(user, exampleMatcher), PageRequest.of(0, 2));
        System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(u));
    }
  • JpaSpecificationExecutor具体使用
    @Test
    public void testSPE() {
//        //模拟请求参数
//        User userQuery = User.builder()
//                .username("jack")
//                .gender(Gender.MAN)
//                .age("20")
//                .build();
//        //假设的时间范围参数
//        Instant beginCreateDate = Instant.now().plus(-2, ChronoUnit.HOURS);
//        Instant endCreateDate = Instant.now().plus(1, ChronoUnit.HOURS);
//        //利用Specification进行查询
//        Page<User> users = userRepository.findAll((Specification<User>) (root, query, cb) -> {
//            List<Predicate> ps = new ArrayList<Predicate>();
//            if (StringUtils.isNotBlank(userQuery.getUsername())) {
//                //我们模仿一下like查询,根据name模糊查询
//                ps.add(cb.like(root.get("name"), "%" + userQuery.getUsername() + "%"));
//            }
//            if (userQuery.getGender() != null) {
//                //equal查询条件,这里需要注意,直接传递的是枚举
//                ps.add(cb.equal(root.get("sex"), userQuery.getGender()));
//            }
//            if (userQuery.getAge() != null) {
//                //greaterThan大于等于查询条件
//                ps.add(cb.greaterThan(root.get("age"), userQuery.getAge()));
//            }
//            if (beginCreateDate != null && endCreateDate != null) {
//                //根据时间区间去查询创建
//                ps.add(cb.between(root.get(f"createDate"), beginCreateDate, endCreateDate));
//            }
//            return query.where(ps.toArray(new Predicate[ps.size()])).getRestriction();
//        }, PageRequest.of(0, 2));
//        System.out.println(users);
        User queryWrapper = User.builder().username("wuhaiqing").password("wuhaiqing").gender(Gender.MAN).age("28").build();
        //假设的时间范围参数
        Instant beginCreateDate = Instant.now().plus(-2, ChronoUnit.HOURS);
        Instant endCreateDate = Instant.now().plus(1, ChronoUnit.HOURS);
        Page<User> all = userRepository.findAll((Specification<User>) (root, query, cb) -> {
            List<Predicate> ps = Lists.newArrayList();
            //模糊查询
            if (StringUtils.isNotBlank(queryWrapper.getUsername())) {
                ps.add(cb.like(root.get("username"), "%" + queryWrapper.getUsername() + "%"));
            }
            //精确匹配
            if (StringUtils.isNotBlank(queryWrapper.getAge())) {
                ps.add(cb.equal(root.get("age"), queryWrapper.getAge()));
            }
            //区间
//            if (beginCreateDate != null && endCreateDate != null) {
//                ps.add(cb.between(root.get("createDate"), beginCreateDate, endCreateDate));
//            }
            return query.where(ps.toArray(new Predicate[ps.size()])).getRestriction();
        }, PageRequest.of(0, 1));
    }
  • 自定义reponsitory
    • EntityManager 如何使用

​ 获得 EntityManager 的方式:通过 @PersistenceContext 注解。

@DataJpaTest

@TestInstance(TestInstance.Lifecycle.PER_CLASS)

public class UserRepositoryTest {

    //利用该方式获得entityManager

    @PersistenceContext

    private EntityManager entityManager;

    @Autowired

    private UserRepository userRepository;

    /**

     * 测试entityManager用法

     *

     * @throws JsonProcessingException

     */

    @Test

    @Rollback(false)

    public void testEntityManager() throws JsonProcessingException {

        //测试找到一个User对象

        User user = entityManager.find(User.class,2L);

        Assertions.assertEquals(user.getAddresses(),"shanghai");



        //我们改变一下user的删除状态

        user.setDeleted(true);

        //merger方法

        entityManager.merge(user);

        //更新到数据库里面

        entityManager.flush();



        //再通过createQuery创建一个JPQL,进行查询

        List<User> users =  entityManager.createQuery("select u From User u where u.name=?1")

                .setParameter(1,"jack")

                .getResultList();

        Assertions.assertTrue(users.get(0).getDeleted());

    }

}

  • 如何正确使用 @Entity 里面的回调方法?
image (5).png
  • 语法注意事项。
    • 回调函数都是和 EntityManager.flush 或 EntityManager.commit 在同一个线程里面执行的,只不过调用方法有先后之分,都是同步调用,所以当任何一个回调方法里面发生异常,都会触发事务进行回滚,而不会触发事务提交。
    • Callbacks 注解可以放在实体里面,可以放在 super-class 里面,也可以定义在 entity 的 listener 里面,但需要注意的是:放在实体(或者 super-class)里面的方法,签名格式为“void ()”,即没有参数,方法里面操作的是 this 对象自己;放在实体的 EntityListener 里面的方法签名格式为“void (Object)”,也就是方法可以有参数,参数是代表用来接收回调方法的实体。
    • 使上述注解生效的回调方法可以是 public、private、protected、friendly 类型的,但是不能是 static 和 finnal 类型的方法。
    @PreUpdate
    public void preUpdate() {
        System.out.println("preUpdate::" + this.toString());
    }

    @PostUpdate
    public void postUpdate() {
        System.out.println("postUpdate::" + this.toString());
    }

    @PreRemove
    public void preRemove() {
        System.out.println("preRemove::" + this.toString());
    }

    @PostRemove
    public void postRemove() {
        System.out.println("postRemove::" + this.toString());
    }
  • 乐观锁的实现
    • 如何使用乐观锁?

​ 乐观锁在实际开发过程中很常用,它没有加锁、没有阻塞,在多线程环境以及高并发的情况下 CPU 的利用率是最高的,吞吐量也是最大的。jpa中使用@version实现乐观锁。

  • 悲观锁的实现
image-20210528164545123
SELECT
	user0_.id AS id1_1_,
	user0_.age AS age2_1_,
	user0_.create_date AS create_d3_1_,
	user0_.gender AS gender4_1_,
	user0_.PASSWORD AS password5_1_,
	user0_.salt AS salt6_1_,
	user0_.username AS username7_1_,
	user0_.version AS version8_1_ 
FROM
	USER user0_ 
WHERE
	user0_.username =? FOR UPDATE

如何搞清楚事务、连接池的关系?正确配置是怎样的?
  • acid分别指什么?

    • 原子性: 是指一个事务(Transaction)中的所有操作,要么全部完成,要么全部回滚,而不会有中间某个数据单独更新的操作。事务在执行过程中一旦发生错误,会被回滚(Rollback)到此次事务开始之前的状态,就像这个事务从来没有执行过一样。
    • 原子性: 是指一个事务(Transaction)中的所有操作,要么全部完成,要么全部回滚,而不会有中间某个数据单独更新的操作。事务在执行过程中一旦发生错误,会被回滚(Rollback)到此次事务开始之前的状态,就像这个事务从来没有执行过一样。
    • 持久性: 是指事务处理结束后,对数据的修改进行了持久化的永久保存,即便系统故障也不会丢失,其实就是保存到硬盘。
    • 隔离性: 是指数据库允许多个连接,同时并发多个事务,又对同一个数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时,由于交叉执行而导致数据不一致的现象。而 MySQL 里面就是我们经常说的事务的四种隔离级别,即读未提交(Read Uncommitted)、读提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。
  • 四种 MySQL 事务的隔离级别

    • Read Uncommitted(读取未提交内容):此隔离级别,表示所有正在进行的事务都可以看到其他未提交事务的执行结果。不同的事务之间读取到其他事务中未提交的数据,通常这种情况也被称之为脏读(Dirty Read),会造成数据的逻辑处理错误,也就是我们在多线程里面经常说的数据不安全了。在业务开发中,几乎很少见到使用的,因为它的性能也不比其他级别要好多少。
    • Read Committed(读取提交内容): 此隔离级别是指,在一个事务相同的两次查询可能产生的结果会不一样,也就是第二次查询能读取到其他事务已经提交的最新数据。也就是我们常说的不可重复读(Nonrepeatable Read)的事务隔离级别。因为同一事务的其他实例在该实例处理期间,可能会对其他事务进行新的 commit,所以在同一个事务中的同一 select 上,多次执行可能返回不同结果。这是大多数数据库系统的默认隔离级别(但不是 MySQL 默认的隔离级别)。
    • Repeatable Read(可重读): 这是 MySQL 的默认事务隔离级别,它确保同一个事务多次查询相同的数据,能读到相同的数据。即使多个事务的修改已经 commit,本事务如果没有结束,永远读到的是相同数据,要注意它与Read Committed 的隔离级别的区别,是正好相反的。这会导致另一个棘手的问题:幻读 (Phantom Read),即读到的数据可能不是最新的。这个是最常见的,我们举个例子来说明。
    • Serializable(可串行化):这是最高的隔离级别,它保证了每个事务是串行执行的,即强制事务排序,所有事务之间不可能产生冲突,从而解决幻读问题。如果配置在这个级别的事务,处理时间比较长,并发比较大的时候,就会导致大量的 db 连接超时现象和锁竞争,从而降低了数据处理的吞吐量。也就是这个性能比较低,所以除了某些财务系统之外,用的人不是特别多。
  • MySQL 事务与连接的关系

    • 事务必须在同一个连接里面的,离开连接没有事务可言;
    • MySQL 数据库默认 autocommit=1,即每一条 SQL 执行完自动提交事务;
    • 数据库里面的每一条 SQL 执行的时候必须有事务环境;
    • MySQL 创建连接的时候默认开启事务,关闭连接的时候如果存在事务没有 commit 的情况,则自动执行 rollback 操作;
    • 不同的 connect 之间的事务是相互隔离的。
  • MySQL 事务的两种操作方式

    • 第一种:用 BEGIN、ROLLBACK、COMMIT 来实现。

      • BEGIN开始一个事务
      • ROLLBACK事务回滚
      • COMMIT事务确认
    • 第二种:直接用 SET 来改变 MySQL 的自动提交模式。

      • SET AUTOCOMMIT=0禁止自动提交
      • SET AUTOCOMMIT=1开启自动提交
  • @transaction参数

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EjiyKkml-1623053169267)(https://s0.lgstatic.com/i/image/M00/6E/36/Ciqc1F-yC3-AP_fhAArdN5coRWQ007.png)]

  • spring事务的传播行为

    • REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这个值是默认的。
    • SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
    • MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
    • REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
    • NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
    • NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
    • NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 REQUIRED。
第三章 原理与问题排查
  • 常用配置参数
# 可以配置JPA的实现者的原始属性的配置,如:这里我们用的JPA的实现者是hibernate

# 那么hibernate里面的一些属性设置就可以通过如下方式实现,具体properties里面有哪些,本讲会详细介绍,我们先知道这里可以设置即可

spring.jpa.properties.hibernate.hbm2ddl.auto=none

#hibernate的persistence.xml文件有哪些,目前已经不推荐使用

#spring.jpa.mapping-resources=

# 指定数据源的类型,如果不指定,Spring Boot加载Datasource的时候会根据URL的协议自己判断

# 如:spring.datasource.url=jdbc:mysql://localhost:3306/test 上面可以明确知道是mysql数据源,所以这个可以不需要指定;

# 应用场景,当我们通过代理的方式,可能通过datasource.url没办法判断数据源类型的时候,可以通过如下方式指定,可选的值有:DB2,H2,HSQL,INFORMIX,MYSQL,ORACLE,POSTGRESQL,SQL_SERVER,SYBASE)

spring.jpa.database=mysql

# 是否在启动阶段根据实体初始化数据库的schema,默认false,当我们用内存数据库做测试的时候可以打开,很有用

spring.jpa.generate-ddl=false

# 和spring.jpa.database用法差不多,指定数据库的平台,默认会自己发现;一般不需要指定,database-platform指定的必须是org.hibernate.dialect.Dialect的子类,如mysql默认是用下面的platform

spring.jpa.database-platform=org.hibernate.dialect.MySQLInnoDBDialect

# 是否在view层打开session,默认是true,其实大部分场景不需要打开,我们可以设置成false,

# 22课时我们再详细讲解

spring.jpa.open-in-view=false

# 是否显示sql,当执行JPA的数据库操作的时候,默认是false,在本地开发的时候我们可以把这个打开,有助于分析sql是不是我们预期的

# 在生产环境的时候建议给这个设置成false,改由logging.level.org.hibernate.SQL=DEBUG代替,这样的话日志默认是基于logback输出的

# 而不是直接打印到控制台的,有利于增加traceid和线程ID等信息,便于分析

spring.jpa.show-sql=true

  • 日志常用参数
### 日志级别的灵活运用

## hibernate相关

# 显示sql的执行日志,如果开了这个,show_sql就可以不用了

logging.level.org.hibernate.SQL=debug

# hibernate id的生成日志

logging.level.org.hibernate.id=debug

# hibernate所有的操作都是PreparedStatement,把sql的执行参数显示出来

logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

# sql执行完提取的返回值

logging.level.org.hibernate.type.descriptor.sql=trace

# 请求参数

logging.level.org.hibernate.type=debug

# 缓存相关

logging.level.org.hibernate.cache=debug

# 统计hibernate的执行状态

logging.level.org.hibernate.stat=debug

# 查看所有的缓存操作

logging.level.org.hibernate.event.internal=trace

logging.level.org.springframework.cache=trace

# hibernate 的监控指标日志

logging.level.org.hibernate.engine.internal.StatisticalLoggingSessionEventListener=DEBUG

### 连接池的相关日志

## hikari连接池的状态日志,以及连接池是否完好 #连接池的日志效果:HikariCPPool - Pool stats (total=20, active=0, idle=20, waiting=0)

logging.level.com.zaxxer.hikari=TRACE

#开启 debug可以看到 AvailableSettings里面的默认配置的值都有哪些,会输出类似下面的日志格式

# org.hibernate.cfg.Settings               : Statistics: enabled

# org.hibernate.cfg.Settings               : Default batch fetch size: -1

logging.level.org.hibernate.cfg=debug

#hikari数据的配置项日志

logging.level.com.zaxxer.hikari.HikariConfig=TRACE

### 查看事务相关的日志,事务获取,释放日志

logging.level.org.springframework.orm.jpa=DEBUG

logging.level.org.springframework.transaction=TRACE

logging.level.org.hibernate.engine.transaction.internal.TransactionImpl=DEBUG

### 分析connect 以及 orm和 data的处理过程更全的日志

logging.level.org.springframework.data=trace

logging.level.org.springframework.orm=trace

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值