preface
The project I am working on now uses Spring Data Jpa. I used to use Mybatis. I studied the use of Jpa some time ago.
The current structure of the company’s project has many technical points that I have not practiced, so I am learning these things these days. I will try to update my blog once a week since 2021-5-15.
Update GraphQL next week.
The examples in this article are all at github.com/zhangpanqin… The database uses an in-memory database H2.
JPA
Know the JPA
JPA, short for the Java Persistence API, defines the mapping between Java objects and database tables, as well as the interface specification that defines how CRUD should work at runtime.
Hibernate provides an implementation of JPA. There are other implementations, such as Open Jpa and so on.
Spring Data provides a familiar and consistent spring-based programming model for Data access while retaining the special characteristics of the underlying Data store.
-
Spring Data JPA is used to manipulate relational databases
-
Spring Data MongoDB Is used to operate MongoDB
-
Spring Data Elasticsearch is used to manipulate Es
-
Spring Data Redis is used to operate Redis
The underlying JPA implementation of Spring Data JPA adopts Hibernate, or it can be said to encapsulate Hibernate, providing a unified programming model of Spring.
The unified programming model is: the following code can operate on JPA, ES, Redis, etc., but the annotation on Person is different.
The CrudRepository interface can also be replaced to provide more fine-grained data control for different databases.
public interface PersonRepository extends CrudRepository<Person.Long> {
List<Person> findByLastname(String lastname);
List<Person> findByFirstnameLike(String firstname);
}
Copy the code
Introduction to JPA common annotations
Do not use a database foreign key when using JPA because it affects performance and is not conducive to database replacement.
Instead of using Hibernate to generate table structures, use flyway components to control database tables, indexes, and field management through SQL. Flyway is more flexible
@Data
@Entity
@Table(name = "sys_user")
public class SysUserEntity extends BaseEntity {
private String nickname;
private Integer age;
ReferencedColumnName is the primary key of the current table */
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = USER_ID,referencedColumnName = ID,foreignKey =@ForeignKey(NO_CONSTRAINT) )
private List<SysBlogEntity> sysBlogEntities;
}
Copy the code
@Entity
Flags the class as an Entity and is managed by JPA
@Table
Specify that Entity maps to that table in the database
@JoinColumn
Specifies which two fields are associated between two associated tables
@Column
Specifies that the Entity field is associated with that field in the table
@Id
Specify the primary key field
@GeneratedValue
Specify the primary key generation strategy, as detailed below
@Transient
Ignore the mapping between fields and table fields
@OneToMany
The one-to-many relationship specifies the mapping relationship between the current Entity and another Entity.
ReferencedColumnName is the primary key of the current table */
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = USER_ID,referencedColumnName = ID,foreignKey =@ForeignKey(NO_CONSTRAINT) )
private List<SysBlogEntity> sysBlogEntities;
Copy the code
@ManyToOne
Reference OneToMany
@ManyToMany
/ * * *@JoinTableSpecify the intermediate table, and the field mapping * in the intermediate table@JoinColumn(name = ROLE_ID,referencedColumnName = ID) Specifies that the intermediate columnname is associated with that other columnname */
@ManyToMany
@JoinTable(name = SYS_USER_ROLE, joinColumns = {@JoinColumn(name = USER_ID, referencedColumnName = ID)}, inverseJoinColumns = {@JoinColumn(name = ROLE_ID,referencedColumnName = ID)})
private List<SysRoleEntity> sysRoleEntities;
Copy the code
@OneToOne
@OneToOne(optional=false)
@JoinColumn(name="CUSTREC_ID", unique=true, nullable=false, updatable=false)
private CustomerRecord customerRecord;
Copy the code
@Query
Can write SQL operation database
public interface SysBlogRepository extends JpaRepository<SysBlogEntity.Long> {
@Query(nativeQuery = true,value = "select * from sys_blog where user_id = :userId")
List<SysBlogEntity> findByUserId(Long userId);
@Query(nativeQuery = true ,value = "select * from sys_blog where title = :#{#sysBlogDTO.title}")
List<SysBlogEntity> findByTitle(@Param("sysBlogDTO") SysBlogDTO sysBlogDTO);
}
Copy the code
Primary key generation policy
/** * strategy The value ** AUTO is controlled by the program and the default policy. SQL > alter table IDENTITY * * alter table IDENTITY * * alter table IDENTITY * * alter table IDENTITY * * alter table IDENTITY * * alter table IDENTITY * * alter table IDENTITY * * alter table IDENTITY * * Oracle, PostgreSQL, and DB2 can use * to use sequences as primary keys@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "emailSeq")
* @SequenceGenerator(initialValue = 1, name = "emailSeq", sequenceName = "EMAIL_SEQUENCE") * private long id; Create sequence EMAIL_SEQUENCE; * when sequenceName is not specified in SequenceGenerator, Hibernate provides a hibernate_sequence * * TABLE sequenceName by default
public @interface GeneratedValue {
GenerationType strategy(a) default AUTO;
String generator(a) default "";
}
Copy the code
Example of primary key generation strategy
@Data
@Table
@Entity
public class KeyGeneratorEntity {
@Id
@GeneratedValue(generator = "system-uuid")
@GenericGenerator(name = "system-uuid",strategy = "uuid")
private String id;
private String username;
}
@SpringBootTest
class KeyGeneratorRepositoryTest {
@Autowired
private KeyGeneratorRepository keyGeneratorRepository;
@Test
public void run(a){
final KeyGeneratorEntity keyGeneratorEntity = new KeyGeneratorEntity();
keyGeneratorEntity.setUsername(LocalDateTime.now().toString());
keyGeneratorRepository.save(keyGeneratorEntity);
final List<KeyGeneratorEntity> all = keyGeneratorRepository.findAll();
/ / [KeyGeneratorEntity (id = ff808081796f260c01796f2616aa0000, username = 2021-05-15 T16: incense which. 722)]System.out.println(all); }}Copy the code
Hibernate provides the following primary key generation strategies
When @ GeneratedValue (strategy = GenerationType. SEQUENCE) is using SequenceStyleGenerator. Class control primary key generation.
When @GenericGenerator(name = “system-uuid”,strategy = “uuid”), uuidhexGenerator.class is used
public class DefaultIdentifierGeneratorFactory implements MutableIdentifierGeneratorFactory.Serializable.ServiceRegistryAwareService { private ConcurrentHashMap<String, Class> generatorStrategyToClassNameMap = new ConcurrentHashMap<String, Class>(); @SuppressWarnings("deprecation") public DefaultIdentifierGeneratorFactory(a) { register( "uuid2", UUIDGenerator.class ); register( "guid", GUIDGenerator.class ); // can be done with UUIDGenerator + strategy register( "uuid", UUIDHexGenerator.class ); // "deprecated" for new use register( "uuid.hex", UUIDHexGenerator.class ); // uuid.hex is deprecated register( "assigned", Assigned.class ); register( "identity", IdentityGenerator.class ); register( "select", SelectGenerator.class ); register( "sequence", SequenceStyleGenerator.class ); register( "seqhilo", SequenceHiLoGenerator.class ); register( "increment", IncrementGenerator.class ); register( "foreign", ForeignGenerator.class ); register( "sequence-identity", SequenceIdentityGenerator.class ); register( "enhanced-sequence", SequenceStyleGenerator.class ); register( "enhanced-table", TableGenerator.class ); } public void register(String strategy, Class generatorClass) { LOG.debugf( "Registering IdentifierGenerator strategy [%s] -> [%s]", strategy, generatorClass.getName() ); final Class previous = generatorStrategyToClassNameMap.put( strategy, generatorClass ); if ( previous != null ) { LOG.debugf( " - overriding [%s]", previous.getName() ); } }}
Copy the code
Lazy needs to be executed within a transaction
public class Order1 { @Id @GeneratedValue private Long id; private String description; @OneToMany(fetch = FetchType.LAZY) @JoinColumn(name = "order_id",referencedColumnName = "id",foreignKey =@ForeignKey(NO_CONSTRAINT)) privateList<OrderItem> orderItemList; }// @Transactional(readOnly = true)public List
listOrder(){ System. The out. Println (" -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - began to query -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - "); final List
all = orderRepository.findAll(); System. The out. Println (" -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - begin to lazy loading -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - "); System.out.println(JSON.toJSONString(all)); return all; }
Copy the code
JPA does not query Lazy data when the data needs to be loaded lazily. It queries Lazy data only when it is used, but when used in the same transaction as the original query, otherwise it will throw the following exception
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.mflyyou.jpa.n1.Order1.orderItemList, could not initialize proxy - no Session
Copy the code
Order1.orderitemlist failed because the transaction was not opened and the query transaction was closed after orderRepository.findAll() was executed.
When the Transactional annotation @Transactional is added, the whole method executes within a transaction and no errors are reported.
N + 1 problem
@Entity@Table(name = "order1")@Datapublic class Order1 { @Id @GeneratedValue private Long id; private String description; @OneToMany(fetch = FetchType.LAZY) @JoinColumn(name = "order_id",referencedColumnName = "id",foreignKey =@ForeignKey(NO_CONSTRAINT)) privateList<OrderItem> orderItemList; }@Transactional(readOnly = true)public Order1 findOne(Long id){ System.out.println("--------------------- Start query ---------------------"); final Optional<Order1> byId = orderRepository.findById(id); System.out.println("--------------------- lazy loading ---------------------"); System.out.println(JSON.toJSONString(byId.get())); returnbyId.get(); }Copy the code
When Order1 is queried, orderItemList is not actually queried. It is queried once when orderItemList is used.
When Order1 has N associated attributes, it will query N times to get the corresponding data.
LAZY loading problems occur when data is retrieved in fetchtype. LAZY
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - start query -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- began to query -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - Hibernate: select order1x0_.id as id1_2_0_, order1x0_.description as descript2_2_0_ from order1 order1x0_ where Order1x0_. Id =? -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - begin to lazy loading -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - Hibernate: select orderiteml0_.order_id as order_id3_3_0_, orderiteml0_.id as id1_3_0_, orderiteml0_.id as id1_3_1_, orderiteml0_.name as name2_3_1_, orderiteml0_.order_id as order_id3_3_1_, orderiteml0_.price as price4_3_1_ from order_item orderiteml0_ whereCopy the code
{ "description": "Testing the 2021-05-15 T17:35:50. 349"."id": 1."orderItemList": [{"id": 2."name": "Ceshi2021-05-15 t17:35:50. 423"."orderId": 1."price": 10 }, { "id": 3."name": "Ceshi2021-05-15 t17:35:50. 423"."orderId": 1."price": 10 }, { "id": 4."name": "Ceshi2021-05-15 t17:35:50. 423"."orderId": 1."price": 10 }, { "id": 5."name": "Ceshi2021-05-15 t17:35:50. 423"."orderId": 1."price": 10 }, { "id": 6."name": "Ceshi2021-05-15 t17:35:50. 423"."orderId": 1."price": 10}}]Copy the code
Instead, use @onetomany (fetch = fetchType.eager).
The query gets all the data in one go
Hibernate: select order1x0_.id as id1_2_0_, order1x0_.description as descript2_2_0_, orderiteml1_.order_id as order_id3_3_1_, orderiteml1_.id as id1_3_1_, orderiteml1_.id as id1_3_2_, orderiteml1_.name as name2_3_2_, orderiteml1_.order_id as order_id3_3_2_, orderiteml1_.price as price4_3_2_ from order1 order1x0_ left outer join order_item orderiteml1_ on order1x0_.id=orderiteml1_.order_id where order1x0_.id=?
Copy the code
@onetomany (fetch = FetchType.EAGER) error if orderItemList and orderItemList2 are present
public class Order1 { @Id @GeneratedValue private Long id; private String description; @OneToMany(fetch = FetchType.EAGER) @JoinColumn(name = "order_id",referencedColumnName = "id",foreignKey =@ForeignKey(NO_CONSTRAINT)) private List<OrderItem> orderItemList; @OneToMany(fetch = FetchType.EAGER) @JoinColumn(name = "order_id",referencedColumnName = "id",foreignKey =@ForeignKey(NO_CONSTRAINT)) privateList<OrderItem2> orderItemList2; }Copy the code
@NamedEntityGraph and @EntityGraph can solve the N+1 problem. Can also solve the cascading query, query which member variables, do not query which member variables. It gives us greater freedom to query data based on business.
EntityGraph
@NamedEntityGraph defines which data to query when querying. @EntityGraph is used to mark which NamedEntityGraph is used by Repository.
@Entity@Table(name = "order1")@Data@NamedEntityGraph(name = "searchOrderGraphItem", attributeNodes = { @NamedAttributeNode(value = "orderGraphItemList", subgraph = "OrderGraphItem_productGraphs"), }, subgraphs = { @NamedSubgraph(name = "OrderGraphItem_productGraphs", attributeNodes = { @NamedAttributeNode(value = "productGraphs") }) })public class OrderGraph1 { @Id @GeneratedValue private Long id; private String description; @OneToMany(fetch = FetchType.EAGER) @JoinColumn(name = "order_id", referencedColumnName = "id", foreignKey = @ForeignKey(NO_CONSTRAINT)) private Set<OrderGraphItem> orderGraphItemList; @OneToMany(fetch = FetchType.EAGER) @JoinColumn(name = "order_id", referencedColumnName = "id", foreignKey = @ForeignKey(NO_CONSTRAINT)) privateSet<OrderGraphItem2> orderGraphItemList2; }public interface OrderGraphRepository extends JpaRepository<OrderGraph1.Long> { @EntityGraph(value = "searchOrderGraphItem", type = EntityGraph.EntityGraphType.FETCH) OrderGraph1 findByIdEquals(Long id); }Copy the code
@Testpublic void findById(a) { final OrderGraph1 orderGraph1s = orderGraphService.findById(1L); assertThat(orderGraph1s, notNullValue()); }Copy the code
The type specified in @entitygraph can FETCH and LOAD
- The FETCH for
NamedEntityGraph
The definition of theattributeNodes
Use eager and lazy if undeclared
@EntityGraph(value = "searchOrderGraphItem", type = EntityGraph.EntityGraphType.FETCH)OrderGraph1 findByIdEquals(Long id);
Copy the code
Only the fields in the table corresponding to OrderGraph1 and orderGraphItemList are queried. OrderGraphItemList2 will only be queried when used.
- The LOAD for
NamedEntityGraph
The definition of theattributeNodes
Use eager, undeclared properties configured using propertiesFetchType
@EntityGraph(value = "searchOrderGraphItem", type = EntityGraph.EntityGraphType.LOAD)OrderGraph1 findByIdEquals(Long id);
Copy the code
This will query the fields in the table corresponding to OrderGraph1 and orderGraphItemList. The orderGraphItemList2 property is also checked directly because FetchType.EAGER is configured.
OrderGraphItemList2 property is only detected when orderGraphItemList2 is used if fetchType. Lazy is configured
The audit function
You’ll see in a typical table, the primary key ID, the creation time, the update event, who created it, who updated it, plus the optimistic lock.
Implement AuditorAware and populate the user ID.
@Data@MappedSuperclass@EntityListeners(AuditingEntityListener.class)public abstract class BaseEntity { @Id private Long id; /** * create time */ @CreatedDate @Column(name = "create_date", updatable = false) private Instant createDate; /** * Change the time */ @LastModifiedDate @Column(name = "update_date") private Instant updateDate; /** * who created */ @CreatedBy @Column(name = "create_by", updatable = false) private Integer createBy; /** ** by whom */ @LastModifiedBy @Column(name = "update_by") private Integer updateBy; /** * optimistic lock */ @Version @Column(name = "version") private Long version = 0L; }@Componentpublic class MyAuditorAware implements AuditorAware<Integer> { /** * Get the current login id */ @Override public Optional<Integer> getCurrentAuditor(a) { Id return optional. ofNullable(100); }}
Copy the code
Optimistic lock, the updated version must be equal to the version in the database, otherwise the update will throw an exception. Can also use the Spring – retry capture ObjectOptimisticLockingFailureException retry the update.
@Data@Entity@Table(name = "sys_user")public class SysUserEntity extends BaseEntity { private String nickname; privateInteger age; }Copy the code
@SpringBootTestclass JpaStudyApplicationTests { private static final Long USER_ID_EQUALS_1 = 1L; private static final Long USER_ID_EQUALS_2 = 2L; private static final Long USER_ID_EQUALS_3 = 3L; @Resource private SysUserRepository sysUserRepository; private SysUserEntity saveSysUserEntity; private SysUserEntity saveSysUserEntity2; private SysUserEntity saveSysUserEntity3; @BeforeEach public void beforeEach(a) { saveSysUserEntity = new SysUserEntity(); saveSysUserEntity.setAge(10); saveSysUserEntity.setId(USER_ID_EQUALS_1); saveSysUserEntity.setNickname("Test"); saveSysUserEntity.setVersion(10L); saveSysUserEntity2 = new SysUserEntity(); saveSysUserEntity2.setAge(10); saveSysUserEntity2.setId(USER_ID_EQUALS_2); saveSysUserEntity2.setNickname("Test"); saveSysUserEntity2.setVersion(10L); saveSysUserEntity3 = new SysUserEntity(); saveSysUserEntity3.setAge(10); saveSysUserEntity3.setId(USER_ID_EQUALS_3); saveSysUserEntity3.setNickname("Test"); saveSysUserEntity3.setVersion(10L); } @Test public void should_update_error(a) { sysUserRepository.save(saveSysUserEntity); final Optional<SysUserEntity> byId = sysUserRepository.findById(USER_ID_EQUALS_1); assertThat(byId.isPresent(), is(Boolean.TRUE)); final SysUserEntity sysUserEntity = byId.get(); // sysuserEntity.setVersion (2L); sysUserEntity.setVersion(12L); SysUserEntity. SetNickname (" optimistic locking update "+ LocalDateTime. Now (). The toString ()); final Executable executable = () -> sysUserRepository.save(sysUserEntity); assertThrows(ObjectOptimisticLockingFailureException.class, executable); } @Test public void should_update_success() { sysUserRepository.save(saveSysUserEntity2); Optional
byId = sysUserRepository.findById(USER_ID_EQUALS_2); assertThat(byId.isPresent(), is(Boolean.TRUE)); SysUserEntity sysUserEntity = new SysUserEntity(); sysUserEntity.setId(USER_ID_EQUALS_2); sysUserEntity.setVersion(10L); SysUserEntity. SetNickname (" optimistic locking update "+ LocalDateTime. Now (). The toString ()); SysUserEntity save = sysUserRepository.save(sysUserEntity); assertThat(save.getVersion(), is(11L)); }}
Copy the code