Spring Data JPA fast master

1. Overview of JPA

The full name of JPA is Java Persisitence API, that is, JAVA Persistence API. It is a set of ORM-based specifications withdrawn from sum company. It is internally composed of a series of interfaces and abstract classes. JPA describes the mapping relationship of object-relational tables through JDK5.0 annotations, and persists the entity objects in the runtime to the database.

Advantages of Spring Data: It can operate a variety of databases, relational databases, and non-relational databases. It provides familiar and consistent spring-based programming templates for data access DAO

1. Advantages of JPA

1. Standardization

JPA is one of the javaEE standards released by the JCP organization, so any framework that claims to be compliant with the JPA standard follows the same architecture and provides the same access API, which ensures that enterprise applications developed based on JPA can be used in different environments with a small amount of modification. Run under the JPA framework.

2. Support for container-level features

The JPA framework supports container-level transactions such as large data sets, transactions, and concurrency, making JPA go beyond the limitations of simple persistence frameworks and play a greater role in enterprise applications.

3. Simple and convenient

The main goal of JPA is to provide a simpler programming model: creating an entity under the JPA framework is as simple as creating a Java class, without any constraints and restrictions, and only needs to use javax.persistence.Entity for annotations. The JPA framework and interface are also very simple. There are not many special rules and design requirements, and developers can easily master it. JPA is designed based on non-intrusive principles, so it can be easily integrated with other frameworks or containers 

4. Query ability

The query language of JPA is object-oriented rather than data-oriented. It constructs query statements with object-oriented natural syntax, which can be regarded as the equivalent of Hibernate HQL. JPA defines a unique JPQL, which is a query for entities. Language, the object of operation is an entity, not a table of a relational database, and it can support batch update and modification, JOIN, GROUP BY, HAVING and other advanced query features that are usually only provided by SQL, and even support subqueries

5. Advanced features

JPA supports advanced object-oriented features, such as inheritance between classes, complex relationships between multiple sets and classes, and such support enables developers to use object-oriented models to design enterprise applications without the need to deal with them by themselves. Persistence of these features in relational databases.

2. Relationship between JPA and Hibernate

The JPA specification is essentially an ORM specification. Note that it is not an ORM framework, because the API does not provide an ORM implementation, but only specifies some specifications and provides some programming API interfaces, but the specific implementation is implemented by the service provider.

The relationship between JPA and Hibernate is like the relationship between JDBC and JDBC drivers. JPA is a specification. In addition to being an ORM framework, Hibernate is also a JPA implementation. How does JPA replace Hibernate? Can the Jdbc specification drive the underlying data? No, if you use the jpa specification for database operations, the bottom layer needs hibernate as the implementation to complete the data persistence work.

2. Quick start

1. Configure the data source

The yml method used here, the jpa in it is the name of the database, and you can also write other ones, provided that the database exists, just write your own username and password.

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/jpa?serverTimezone=UTC
    username: root
    password: 123456

2. Import dependencies

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

3. Create an entity class

Let the database know what our table looks like through annotations

Here you can know that the name of the table is users (of course you can name it arbitrarily), and the creation of the table generally has a primary key and an auto-increment operation, all of which are done here through annotations. When the table is created for the first time here, the table name will become popular. We need to manually specify the database for him, which is the database when the data source is configured.

import lombok.Data;

import javax.persistence.*;

@Data
@Entity //这是实体类
@Table(name = "user") //对应哪张表
public class User {

    @Id //这是主键
    @Column(name = "id")//数据库中的id,对应属性中的id
    @GeneratedValue(strategy = GenerationType.IDENTITY) //
    int id;

    @Column(name = "username")//数据库中的username,对应属性中的username
    String username;

    @Column(name = "password")//数据库中的password,对应属性中的password
    String password;

}

Here we also need to set the automatic table definition ddl-auto, you can generate the table yourself without the developer building the table yourself

spring:
  jpa:
    show-sql: true #显示sql
    hibernate:
      ddl-auto: create #自动生成表

The ddl-auto attribute is used to set the automatic table definition, which can automatically create a table for us in the database. The structure of the table will be determined according to the entity class we defined. There are 4 types of it

  • Create deletes the table in the database when it starts, then creates it, and does not delete the data table when it exits
  • Create-drop deletes the table in the database when it starts, then creates it, deletes the data table when it exits, and reports an error if the table does not exist
  • update If the table format is inconsistent at startup, update the table and keep the original data
  • validate The project startup table structure is verified, and if it is inconsistent, an error will be reported

4. Start the test class to complete the table creation

At this time, you can see that the console prints these two sentences, and you will find that the table has been created successfully (it is very similar to typing on the command line yourself, is it very convenient for hibernate to help us complete these operations) The reason for deleting the table is because you chose The create strategy creates a table, and other strategies will be discussed later. 

5. Visit our watch

The first generic type fills in the entity class, and the second one is the type of id

@Repository
public interface UserRepository extends JpaRepository<User,Integer> {

}

 When we want to use:

@SpringBootTest
class YdljpaApplicationTests {

    @Autowired
    UserRepository userRepository;
    
    @Test
    public void testQuery(){
        userRepository.findById(1).ifPresent(System.out::println);
    }
    
}

Here you need to pay attention to changing the create strategy to update, because the create strategy will delete the data in the table

Increment operation

    @Test
    public void testAdd(){
        User user=new User();
        //user.setId(2);
        user.setUsername("itnanls");
        user.setPassword("ydl666");

        User saveUser = userRepository.save(user); //新增 返回的实体中带着实体id
        System.out.println(saveUser);
    }

delete operation

    @Test
    public void testDel(){
        userRepository.deleteById(3);
    }

pagination

    @Test
    public void testPageable(){
        userRepository.findAll(PageRequest.of(0,2)).forEach(System.out::println);
    }

3. Method naming rule query

As the name implies, method naming rule query is to create a query based on the name of the method. You only need to define the name of the method according to the method naming rules provided by Spring Data JPA to complete the query work. Spring Data JPA will analyze according to the method name when the program is executed, and automatically generate query statements for query.

According to the rules defined by Spring Data JPA, the query method starts with findBy. When it comes to conditional queries, the properties of the conditions are connected with the conditional keywords. It should be noted that the first letter of the conditional properties should be capitalized. When the framework parses the method name, it first intercepts the redundant prefix of the method name, and then parses the rest.

@Repository
public interface UserRepository extends JpaRepository<User,Integer> {

    //根据username查询
    List<User> findAllByUsername(String str);

    //根据用户名和密码查询
    List<User> findByUsernameAndPassword(String username,String password);

    //根据用户名模糊查询
    List<User> findByUsernameLike(String username);

}

The specific keywords, usage methods and generated SQL are shown in the table below

Keyword Sample JPQL
And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
Is,Equals findByFirstnameIs,findByFirstnameEquals … where x.firstname = ?1
Between findByStartDateBetween … where x.startDate between ?1 and ?2
LessThan findByAgeLessThan … where x.age < ?1
LessThanEqual findByAgeLessThanEqual … where x.age ⇐ ?1
GreaterThan findByAgeGreaterThan … where x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1
After findByStartDateAfter … where x.startDate > ?1
Before findByStartDateBefore … where x.startDate < ?1
IsNull findByAgeIsNull … where x.age is null
IsNotNull,NotNull findByAge(Is)NotNull … where x.age not null
Like findByFirstnameLike … where x.firstname like ?1
NotLike findByFirstnameNotLike … where x.firstname not like ?1
StartingWith findByFirstnameStartingWith … where x.firstname like ?1 (parameter bound with appended %)
EndingWith findByFirstnameEndingWith … where x.firstname like ?1 (parameter bound with prepended %)
Containing findByFirstnameContaining … where x.firstname like ?1 (parameter bound wrapped in %)
OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc
Not findByLastnameNot … where x.lastname <> ?1
In findByAgeIn(Collection ages) … where x.age in ?1
swimming findByAgeNotIn(Collection age) … where x.age not in ?1
TRUE findByActiveTrue() … where x.active = true
FALSE findByActiveFalse() … where x.active = false
IgnoreCase findByFirstnameIgnoreCase … where UPPER(x.firstame) = UPPER(?1)

4. Use JPQL to query

Using the query method provided by Spring Data JPA can already solve most of the application scenarios, but for some businesses, we also need to flexibly construct query conditions. At this time, we can use the @Query annotation to complete the query in combination with JPQL statements .

The use of the @Query annotation is very simple. Just mark the annotation on the method and provide a JPQL query statement at the same time. Note:

  • Replace * with aliases in most cases
  • Change table name to class name
  • Change field name to property name
  • Use with annotation @Query
@Query("select 表别名 from 表名(实际为类名) 别名 where 别名.属性='itlils'")
public List<User> findUsers();

The simplest query

Note that it is an object-oriented query, so from is not a table, and the User object

@Query("select u from User u")//从实体类,查询,而不是表
List<User> findAllUser();

filter criteria

    @Query("select u from User u where u.username='itlils'")//从实体类,查询,而不是表。where不是列名,而是属性名
    List<User> findAllUserByUsername();

projection result

    @Query("select u.id from User u")//从实体类,查询,而不是表
    List<Integer> findAllUser2();

aggregation query

    @Query("select count(u) from User u")//从实体类,查询,而不是表
    List<Integer> findAllUser4();

pass parameters

    //修改数据 一定加上@Modifying 注解
    @Transactional
    @Modifying
    @Query("update User set username=?1 where id=?2")
    int updateUsernameById2(String username,Integer id);

Use native sql

To use native sql, you need to add nativeQuery = true after the sql statement

    //修改数据 一定加上@Modifying 注解
    @Transactional
    @Modifying
    @Query(value = "update user u set u.username=?1 where u.id=?2",nativeQuery = true)
    int updateUsernameById3(String username,Integer id);

Five, one-to-one relationship

We use the account table and account detail table as examples

@Data
@Entity   //表示这个类是一个实体类
@Table(name = "account")    //对应的数据库中表名称
public class Account {

    @GeneratedValue(strategy = GenerationType.IDENTITY)   //生成策略,这里配置为自增
    @Column(name = "id")    //对应表中id这一列
    @Id     //此属性为主键
    int id;

    @Column(name = "username")   //对应表中username这一列
    String username;

    @Column(name = "password")   //对应表中password这一列
    String password;
    
    //一对一
    @JoinColumn(name = "detail_id")
    @OneToOne//声明为一对一关系
    AccountDetail detail;//对象类型,也可以理解这里写哪个实体类,外键就指向哪个实体类的主键
}
@Data
@Entity
@Table(name = "account_details")
public class AccountDetail {
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)//还是设置一个自增主键
    @Id
    int id;

    @Column(name = "address")
    String address;

    @Column(name = "email")
    String email;

    @Column(name = "phone")
    String phone;

    @Column(name = "real_name")
    String realName;
}

Here we can see from the log that hibernate helps us complete the creation of foreign keys

But this still can't complete the operation of two tables at the same time. Set lazy loading to complete the function of checking whatever you want, set the association level to complete the operation of two tables at the same time

@JoinColumn(name = "detail_id")
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) //设置关联操作为ALL
AccountDetail detail;

There are also multiple levels of association here, generally set to all

  • ALL: All operations are associated operations
  • PERSIST: The association operation is performed only when the operation is inserted
  • REMOVE: The association operation is performed only when the operation is deleted
  • MERGE: The association operation is performed only when the modification operation is performed

Now we can add data and delete at the same time

@Autowired
    AccountRepository accountRepository;

    @Test
    public void testFind11() {
        Account account=new Account();
        account.setUsername("itlils");
        account.setPassword("ydl666");

        AccountDetail accountDetail=new AccountDetail();
        accountDetail.setPhone("13000000000");
        accountDetail.setRealName("itlils");
        accountDetail.setAddress("cn");
        accountDetail.setEmail("[email protected]");

        account.setDetail(accountDetail);

        Account save = accountRepository.save(account);

        System.out.println("插入之后account id为:"+save.getId()+"|||accountDetail id"+save.getDetail().getId());

    }

Six, one-to-many relationship

Let's use the author and article as an example. In a one-to-many situation, there must be a field in the many places to identify the author

Author and Article is a one-to-many relationship (bidirectional). So in JPA, how to represent a one-to-many bidirectional association?

JPA uses @OneToMany and @ManyToOne to identify one-to-many bidirectional associations. One end (Author) uses @OneToMany, and the multi-end (Article) uses @ManyToOne.

In the JPA specification, a one-to-many bidirectional relationship is maintained by a multi-terminal (Article). That is to say, the multi-end (Article) is the relationship maintenance end, which is responsible for adding, deleting, modifying and checking the relationship. One end (Author) is the maintained end of the relationship and cannot maintain the relationship.

One end (Author) uses the mappedBy="author" attribute of @OneToMany annotation to indicate that Author is the end of the relationship.

Multi-terminal (Article) uses @ManyToOne and @JoinColumn to annotate the attribute author, @ManyToOne indicates that Article is multi-terminal, and @JoinColumn sets the associated field (foreign key) in the article table.

@Data
@Entity   //表示这个类是一个实体类
@Table(name = "author")    //对应的数据库中表名称
public class Author {
    @Column(name = "id")
    @Id // 主键
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增长策略
    private Long id; //id


    @Column(nullable = false, length = 20,name = "name")
    private String name;//姓名

    @OneToMany(mappedBy = "author",cascade=CascadeType.ALL,fetch=FetchType.LAZY)
    //级联保存、更新、删除、刷新;延迟加载。当删除用户,会级联删除该用户的所有文章
    //拥有mappedBy注解的实体类为关系被维护端
    //mappedBy="author"中的author是Article中的author属性
    private List<Article> articleList;//文章列表
}
@Data
@Entity   //表示这个类是一个实体类
@Table(name = "article")    //对应的数据库中表名称
public class Article {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增长策略
    @Column(name = "id", nullable = false)
    private Long id;


    @Column(nullable = false, length = 50) // 映射为字段,值不能为空
    private String title;

    @Lob  // 大对象,映射 MySQL 的 Long Text 类型
    @Basic(fetch = FetchType.LAZY) // 懒加载
    @Column(nullable = false) // 映射为字段,值不能为空
    private String content;//文章全文内容

    @ManyToOne(cascade={CascadeType.MERGE,CascadeType.REFRESH},optional=false)//可选属性optional=false,表示author不能为空。删除文章,不影响用户
    @JoinColumn(name="author_id")//设置在article表中的关联字段(外键)
    private Author author;//所属作者
}

Add

    @Autowired
    AuthorRepository authorRepository;
    @Autowired
    ArticleRepository articleRepository;
    @Test
    public void testFind13() {
        Author author=new Author();
        author.setName("itlils");
        Author author1 = authorRepository.save(author);


        Article article1=new Article();
        article1.setTitle("1");
        article1.setContent("123");
        article1.setAuthor(author1);
        articleRepository.save(article1);


        Article article2=new Article();
        article2.setTitle("2");
        article2.setContent("22222");
        article2.setAuthor(author1);
        articleRepository.save(article2);


    }

delete

    @Test
    public void testFind14() {
        articleRepository.deleteById(2L);
    }

Inquire

    @Transactional
    @Test
    public void testFind15() {
        Optional<Author> byId = authorRepository.findById(1L);
        if (byId.isPresent()){
            Author author = byId.get();
            List<Article> articleList = author.getArticleList();
            System.out.println(articleList);
        }
    }

important point:

Lazy loading will make it impossible to display the author. We only need to set: author fetch=FetchType.EAGER to cancel the lazy loading.

However, there will also be a problem of circular dependency. When tostring an article, an author is required, and the author also needs an article. The solution: cycle toString and destroy one party's toString. Article plus @ToString(exclude = {"author"})

Seven, many-to-many relationship

Users and permissions, one permission can have multiple users, and one user can have multiple permissions. How to deal with this many-to-many, this time requires an intermediate association table to do it.

JPA uses @ManyToMany to annotate many-to-many relationships, which are maintained by an association table. The default table name of this associated table is: main table name + underscore + slave table name . (The master table refers to the table corresponding to the relationship maintenance end, and the slave table refers to the table corresponding to the relationship maintenance end). This association table has only two foreign key fields, pointing to the master table ID and the slave table ID respectively. The default name of the field is: main table name + underscore + primary key column name in the main table , secondary table name + underscore + primary key column name in the secondary table .

Note:

1. Operations such as cascade save, cascade delete, and cascade update are generally not set in many-to-many relationships.

2. You can freely designate one party as the relationship maintenance end. In this example, I designate User as the relationship maintenance end, so the name of the generated association table is: user_authority, and the fields of the association table are: user_id and authority_id.

3. The binding of the many-to-many relationship is completed by the relationship maintenance terminal, that is, the many-to-many relationship is bound by User.setAuthorities(authorities). The maintained end of the relationship cannot bind the relationship, that is, the User cannot bind the relationship.

4. The release of the many-to-many relationship is completed by the relationship maintenance terminal, that is, the many-to-many relationship is released by Authority.getUser().remove(user). The maintained end of the relationship cannot cancel the relationship, that is, the Authority cannot cancel the relationship.

5. If a many-to-many relationship has been bound between User and Authority, then Authority cannot be deleted directly, and Authority can only be deleted after the User terminates the relationship. But User can be deleted directly, because User is the relationship maintenance end, when deleting User, the relationship between User and Authority will be canceled first, and then Authority will be deleted.

@Data
@Entity //这是实体类
@Table(name = "users") //对应哪张表
public class Users {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;


    @Column(nullable = false, length = 20, unique = true)
    private String username; // 用户账号,用户登录时的唯一标识


    @Column(length = 100)
    private String password; // 登录时密码

    @ManyToMany
    @JoinTable(name = "user_authority",joinColumns = @JoinColumn(name = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "authority_id"))
    //1、关系维护端,负责多对多关系的绑定和解除
    //2、@JoinTable注解的name属性指定关联表的名字,joinColumns指定外键的名字,关联到关系维护端(User)
    //3、inverseJoinColumns指定外键的名字,要关联的关系被维护端(Authority)
    //4、其实可以不使用@JoinTable注解,默认生成的关联表名称为主表表名+下划线+从表表名,
    //即表名为user_authority
    //关联到主表的外键名:主表名+下划线+主表中的主键列名,即user_id
    //关联到从表的外键名:主表中用于关联的属性名+下划线+从表的主键列名,即authority_id
    //主表就是关系维护端对应的表,从表就是关系被维护端对应的表
    private List<Authority> authorityList;

}
@Data
@Entity //这是实体类
@Table(name = "authority") //对应哪张表
public class Authority {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(nullable = false)
    private String name; //权限名

    @ManyToMany(mappedBy = "authorityList")
    private List<Users> userList;
}

Add to

    @Autowired
    private UsersRepository usersRepository;
    @Autowired
    private AuthorityRepository authorityRepository;
    @Test
    public void testFind16() {
        Authority authority = new Authority();
        authority.setId(1);
        authority.setName("ROLE_ADMIN");
        authorityRepository.save(authority);
    }

    @Test
    public void testFind17() {
        Users users = new Users();
        users.setUsername("itlils");
        users.setPassword("123456");
        Authority authority = authorityRepository.findById(1).get();
        List<Authority> authorityList = new ArrayList<>();
        authorityList.add(authority);
        users.setAuthorityList(authorityList);
        usersRepository.save(users);
    }

First run saveAuthority to add a permission record,

Then run saveUser to add a user record, and at the same time, a record is automatically inserted in the user_authority table

delete

    @Test
    public void testFind18() {
        usersRepository.deleteById(1L);
    }

Delete a record in the user table, and user_authority can delete a record in cascade, and the intermediate table and users table data are deleted

Guess you like

Origin blog.csdn.net/weixin_54232666/article/details/130449934