SpringDataJPA基础

news2024/11/25 10:26:41

简介

Spring Data为数据访问层提供了熟悉且一致的Spring编程模版,对于每种持久性存储,业务代码通常需要提供不同存储库提供对不同CURD持久化操作。Spring Data为这些持久性存储以及特定实现提供了通用的接口和模版。其目的是统一简化对不同类型持久性存储的访问。

JPA 全称为Java Persistence API(2019年重新命名为Jakarta Persistence API),是SUN官方提供一种ORM规范、O:Object R:Rlational M: Mapping

规范:

1、ORM映射元素数据:JPA支持xml和注解两种元数据的形式,元数据描述对象和表之间的映射关系,框架据此实体对象持久化到数据表中,如@Enity@Table@Id@Column注解等

2、JPA的API:用来操作实体对象,执行CRUD操作,框架在后台替我们完成所有事情,开发者从繁琐的JDBC和SQL代码中解脱出来

3、JPQL查询语句:通过面向对象而非面向数据库的查询语句数据,避免程序的SQL语句紧密耦合,如from User where name = ?

Spring Data特性

1、模版制作:Spring Data提供了对不同数据库对应的模版,例如MongoTemplate、RedisTemplate、JDBCTemplate。模版提供存储特定CRUD操作,Spring Data JPA不提供模版,只是Spring Data JPA本身就是对JDBC API上的抽象。

2、对象/数据存储映射:可以通过xml文件或注解来映射数据之间的关系

代码示例:

@Entity
@Table(name = "User")
public class User {
  @Id
  private String id;
  @Column(name = "user_name")
  private String name;
  private Date lastLogin;
  @OneToMany
  private List<Role> roles;
}

3、对Respository支持:对持久层提供了基础的CRUD操作以及分页操作等

Hibernate入门案例

在项目目录下的resource创建一个hibernate.cfg.xml文件

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <!--配置数据库连接信息-->
        <property name="connection.url">jdbc:mysql://localhost:3306/spring_data_jpa</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password">123456</property>
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <!--选择数据库类型-->
        <property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
        <!--打印sql语句-->
        <property name="hibernate.show_sql">true</property>
        <!--格式化sql-->
        <property name="hibernate.format_sql">true</property>
        <!-- 表的生成策略,自动生成,默认不自动生成 -->
        <property name="hbm2ddl.auto">update</property>
        <!-- 映射实体类 -->
        <mapping class="org.example.entity.Customer"/>
    </session-factory>
</hibernate-configuration>

其中映射的实体类如下:

@Entity
@Data
@Table(name = "cst_customer")
public class Customer {
    /***
     * @Id 声明主键配置
     * @GeneratedValue 配置主键生成策略
     * @@Column 配置属性和字段映射关系
     * @author Tang
     * @date 2023/10/8 22:38:25
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cust_id")
    private Long custId;
    @Column(name = "cust_name")
    private String custName;
    @Column(name = "cust_address")
    private String custAddress;
}

写入数据:

public class HibernateApp {
    public static void main(String[] args) {
        try (Session session = initSessionFactory().openSession()) {
            Transaction tx = session.beginTransaction();
            Customer customer = new Customer();
            customer.setCustName("张三");
            session.save(customer);
            tx.commit();
        } catch (Exception e) {
            System.out.println(e);
        }

    }

    public static SessionFactory initSessionFactory() {
        return new MetadataSources(new StandardServiceRegistryBuilder().configure("/hibernate.cfg.xml").build()).buildMetadata().buildSessionFactory();
    }
}

具体的CRUD操作详细见文档连接 文档

在项目跟目录下的resource创建META-INF目录,并在其中创建persistence.xml文件,其配置如下

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
    <!-- jpa持久化单元 -->
    <persistence-unit name="springJpa" transaction-type="RESOURCE_LOCAL">
        <!--     jpa实现方式 -->
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <!-- 配置需要进行ORM的POJO类      -->
        <class>org.example.entity.Customer</class>
        <properties>
            <property name="javax.persistence.jdbc.user" value="root"/>
            <property name="javax.persistence.jdbc.password" value="123456"/>
            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/spring_data_jpa"/>
            
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.hbm2ddl.auto" value="update"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>
        </properties>
    </persistence-unit>
</persistence>

配置持久化单元并写入到数据库中

 public static void main(String[] args) {
   EntityManager entityManager = init().createEntityManager();
   EntityTransaction tx = entityManager.getTransaction();
   tx.begin();
   // 持久化操作, 写入到mysql表中
   Customer customer = new Customer();
   customer.setCustName("hello jpa");
   entityManager.persist(customer);
   tx.commit();
 }

public static EntityManagerFactory init() {
  // 配置jpa持久化单元名称
  return Persistence.createEntityManagerFactory("springJpa");
}

使用JPQL语句

JPQL代表Java持久化查询语言。它被用来创建针对实体的查询存储在关系数据库中。 JPQL是基于SQL语法的发展。但它不会直接影响到数据库。

JPQL可以检索使用SELECT子句中的数据,可以使用 UPDATE子句做批量UPDATE和DELETE子句。

使用JPQL语句进行更新

public static void main(String[] args) {
  EntityManager entityManager = init().createEntityManager();
  EntityTransaction tx = entityManager.getTransaction();
  tx.begin();
  // 这里的Customer是映射的实体类名称,其查询都是使用映射实体类的属性
  String jpql = "UPDATE Customer set custName=:custName where custId=:id";
  entityManager.createQuery(jpql)
    .setParameter("custName", "tang")
    .setParameter("id", 3L)
    .executeUpdate();
  tx.commit();
}

也可以使用sql语句来进行对数据库的操作,代码变更如下:

String sql = "UPDATE cst_customer set cust_name=:custName where cust_id=:id";
entityManager.createNativeQuery(sql)
  .setParameter("custName", "tang")
  .setParameter("id", 3L)
  .executeUpdate();

其中的cst_customer是表结构,cust_namecust_id是表的字段名

JPA对象四种状态

临时状态:刚创建出来,没有与entityManager发生关系,没有被持久化,不处于entityManager的对象中

持久状态:与entityManager发生关系,已经被持久化,可以把持久化状态当做实实在在的数据库记录

删除状态:执行remove方法,事务提交之前

游离状态:游离状态就是提交到数据库后,事务commit后实体的状态,因为事务已经提交了,此时实体属性你如何改变都不会同步到数据库中。

在这里插入图片描述

persist方法可以将实例转换为managed状态,在调用flush()方法或事务提交后,实例将被插入到数据库中

Spring Data JPA入门

Spring Data JPASpring提供的一套简化JPA开发的框架,按照约定好的规则进行方法命名来实现dao层接口,就可以在不写接口实现的前提下,实现对数据库的访问和操作。同时提供了很多除了CRUD在外的功能,如分页、排序、复杂查询等功能

引入maven依赖

<dependencies>
  <!-- 统一管理Spring Data子项目的版本 -->
  <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-bom</artifactId>
    <version>2021.1.0</version>
    <type>pom</type>
  </dependency>
  <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
    <version>2.7.7</version>
  </dependency>

  <dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-entitymanager</artifactId>
    <version>5.6.15.Final</version>
  </dependency>
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.33</version>
  </dependency>
  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.15</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.1.10.RELEASE</version>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.10</version>
    <scope>provided</scope>
  </dependency>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
  </dependency>
</dependencies>

resource文件夹下创建Spring.xml文件进行对JPA的配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/data/jpa
    https://www.springframework.org/schema/data/jpa/spring-jpa.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    <!--  整合jpa 配置entityManagerFactory以及对应的事务  -->
    <jpa:repositories base-package="org.example.repositories"
                      entity-manager-factory-ref="entityManagerFactory"
                        transaction-manager-ref="transactionManager"/>
    <!-- 配置EntityManagerFactory的Bean   -->
    <bean name="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="generateDdl" value="true"/>
                <property name="showSql" value="true"/>
            </bean>
        </property>
        <property name="packagesToScan" value="org.example.entity"/>
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 配置数据源   -->
    <bean class="com.alibaba.druid.pool.DruidDataSource" name="dataSource">
        <property name="url" value="jdbc:mysql://localhost:3306/spring_data_jpa"/>
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
    <!-- 配置说明式事务   -->
    <bean class="org.springframework.orm.jpa.JpaTransactionManager" name="transactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>
    <!--  配置注解式事务   -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

其中Spring.xml文件的配置可以等同于如下配置类

@Configuration
@EnableJpaRepositories(basePackages = "org.example.repositories")
@EnableTransactionManagement
public class SpringDataJpaConfig {
    @Bean
    public DataSource dataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("123456");
        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        druidDataSource.setUrl("jdbc:mysql://localhost:3306/spring_data_jpa");
        return druidDataSource;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(true);
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan("org.example.entity");
        factory.setDataSource(dataSource());
        return factory;
    }

    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {

        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(entityManagerFactory);
        return txManager;
    }
}

Spring data jpa 增删改查

// 使用配置类直接变更为@ContextConfiguration(classes = SpringDataJpaConfig.class)
@ContextConfiguration(locations = "/Spring.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringDataJPATest {
    @Autowired
    CustomerRepositories customerRepositories;
    @Test
    public void testQuery() {
        Optional<Customer> customer = customerRepositories.findById(1L);
        customer.ifPresent(System.out::println);
    }
    @Test
    public void testInsert() {
        Customer customer = new Customer();
        customer.setName("hello jpa test");
        customerRepositories.save(customer);
    }
    @Test
    public void testUpdate() {
        Customer customer = new Customer();
        customer.setId(3L);
        customer.setName("hello jpa test update");
        customerRepositories.save(customer);
    }
    @Test
    public void testDelete() {
        Customer customer = new Customer();
        customer.setId(3L);
        customerRepositories.delete(customer);
    }
}

Spring Data Repositories

Spring Data Respositories的抽象目的是为了减少各种持久层存储实现数据访问层所需的样板代码。其中CrudRepository提供了简单的增删改查的操作。PagingAndSortingRepository在增删改查的基础上添加了分页排序的操作。

PagingAndSortingRepository演示代码

@Autowired
CustomerRepositories customerRepositories;
@Test
public void testPaging() {
  Page<Customer> all = customerRepositories.findAll(PageRequest.of(0, 2));
  System.out.println(all.getContent());
}
@Test
public void testSort() {
  // 通过对实体类的id属性进行降序排列
  Iterable<Customer> sorted = customerRepositories.findAll(Sort.sort(Customer.class)
                      .by(Customer::getId).descending());
  System.out.println(sorted);
}

如果需要多个字段进行排序,代码变更如下

Iterable<Customer> sorted = customerRepositories.findAll(Sort.sort(Customer.class)
                .by(Customer::getId).descending()
                .and(Sort.sort(Customer.class)
                        .by(Customer::getName)));

JPQL和SQL

Spring Data JPA提供了多种自定义操作,比如使用JPQL或者原始的SQL、规定的方法名称、Query by Examply、通过Specifications、通过Querydsl方式

使用JPQL进行查询

JPQL语法格式详细见 JPQL语法文档

Repositories进行改造

public interface CustomerRepositories extends PagingAndSortingRepository<Customer, Long> {
    @Query("FROM Customer where name=?1")
    Customer findCustomerByName(String name);
}

也可以通过具名参数来进行JPQL语句的条件查询

public interface CustomerRepositories extends PagingAndSortingRepository<Customer, Long> {
    @Query("FROM Customer where name=:customerName")
    Customer findCustomerByName(@Param("customerName") String name);
}

执行增删改操作

Spring Data JPA中对增删改都需要加上事务,同时添加@Modifying注解通知Spring Data JPA执行增删改操作

public interface CustomerRepositories extends PagingAndSortingRepository<Customer, Long> {
    @Transactional
    @Modifying
    @Query("UPDATE Customer c set c.name=:custName where c.id=:id")
    int updateCustomer(@Param("custName") String custName, @Param("id") Long id);
}

其中c.namec.id都是实体类的属性名,如果是新增一定只能是在Hibernate下才能支持而且必须是INSERT INTO SELECT的方式来进行插入,其他方法不支持新增方法

使用原生SQL

public interface CustomerRepositories extends PagingAndSortingRepository<Customer, Long> {
    @Query(value = "SELECT * FROM cst_customer where cust_name=:custName", nativeQuery = true)
    List<Customer> findCustomerByName(@Param("custName") String name);
}

其中cst_customer是数据库表名,cust_name是数据库表字段,当使用原生SQL时需要在@Query注解的nativeQuery设置为true

指定方法规则名称

支持查询的主题关键字(前缀)

决定方法的作用、只支持查询和删除的操作

关键字描述
find...Byread..Byget...Byquery...Bysearch..Bystream..By通用查询方法通常返回存储库类型、CollectionStreamable子类型或结果包装类,例如PageGeoResults或任何其他特定与商店的结果包装类。可以作findBy..findMyDomainTypeBy...或与其他关键字结合使用
exists..By存在投影。通常返回boolean结果
count..By计数投影返回数字结果
delete...Byremove...By删除查询方法返回无结果void或者删除计数
..First<number>......Top<number>..将查询结果限制为第一个<number>结果。此关键字可以出现主题的find(和其他关键字)和之间的任何位置By
...Distinct...使用不同查询仅返回唯一结果。查阅特定与商店的问题是否支持该功能。此关键字可以出现在主题find(和其他关键字)和之间任何位置By

支持查询方法谓词关键字和修饰符

决定查询条件

关键词样本JPQL片段
DistinctfindDistinctByLastnameAndFirstnameselect distinct .. where x.lastname = ?1 and x.firstname = ?2
AndfindByLastnameAndFirstname.. where x.lastname = ?1 and x.firstname = ?2
OrfindByLastnameOrFirstname..where x.lastname = ?1 and x.firstname = ?2
IsEqualsfindByFirstnamefindByFirstnameIsfindByFirstnameEquals..where x.firstname = ?1
BetweenfindByStartDateBetween...where x.startDate between ?1 and ?2
LessThanfindByAgeLessThan...where x.age < ?1
LessThanEqualfindByAgeLessThanEqual..where x.age >= ?1
GreaterThanfindByAgeGreaterThan..where x.age > ?1
GreaterThanEqualfindByAgeGreaterThanEqual..where x.age >= ?1
AfterfindByStarterDateAfter..where x.startDate > ?1
BeforefindByStartDateBefore..where x.startDate < ?1
IsNullNullfindByAge(Is)Null..where x.age is null
IsNotNullNotNullfindByAge(Is)NotNull..where x.age not null
LikefindByFirstnameLike..where x.firstname like ?1 自己指定%位置
NotLikefindByFirstnameNotLike..where x.firstname not like ?1自己指定%位置
StartingWithfindByFirstnameStartingWith..where x.firstname like ?2(参数绑定了append%
EndingWithfindByFirstnameEndingWith..where x.firstname like ?1(参数绑定了prepended%)
ContainingfindByFirstnameContaining..where x.firstname like ?1(参数绑定包裹在%)
OrderByfindByAgeOrderByLastnameDesc..where x.age = ?1 order by x.lastname desc
NotfindByLastnameNot..where x.lastname <> ?1
InfindByAgeIn(Collection<Age> ages)..where x.age in ?1
NotInfindByAgeNotIn(Collection<Age> ages)..where x.age not in ?1
TruefindByActiveTrue()..where x.active = true
FalsefindByActiveFalse()..where x.active = false
IgoreCasefindByFirstnameIgoreCase..where UPPER(x.firstname) = UPPER(?1)

详细查阅官方文档官方文档

代码示例
public interface CustomerMethodNameRepositories extends PagingAndSortingRepository<Customer, Long> {
    List<Customer> findByName(String name);
}

其中的name是实体类中Customer类中的name属性

删除操作
public interface CustomerMethodNameRepositories extends PagingAndSortingRepository<Customer, Long> {
    @Transient
    @Modifying
    void deleteById(Long id);
}

需要使用@Transient开启事务以及使用@Modifying告诉Spring Data JPA这是增删改操作

模糊查询
public interface CustomerMethodNameRepositories extends PagingAndSortingRepository<Customer, Long> {
    List<Customer> findByNameLike(String name);
}

调用方法是需要指定%,如下

@ContextConfiguration(classes = SpringDataJpaConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class MethodNameTest {
    @Autowired
    private CustomerMethodNameRepositories customerMethodNameRepositories;
    @Test
    public void queryLikeTest() {
        List<Customer> customers = customerMethodNameRepositories.findByNameLike("%test%");
        System.out.println(customers);
    }
}

动态条件查询

Query By Example

只支持查询,不支持嵌套或者分组的属性约束,如firstname=?0或者firstname=?1 and lastname=?2,只支持字符串 start/contains/ends/regex匹配和其他属性类型精确匹配。

详细查询官方文档 使用Ctrl + F 搜索by example

查询所有
public interface QueryByExampleRepositories extends PagingAndSortingRepository<Customer, Long>,
        QueryByExampleExecutor<Customer> {
}

接口需要继承QueryByExampleExecutor,同时查询时需要通过Example.of()构建查询条件

@Test
public void queryTest() {
  // 查询条件,通过Example.of()构建查询条件
  Customer customer = new Customer();
  customer.setName("test");
  Iterable<Customer> all = queryByExampleRepositories.findAll(Example.of(customer));
  System.out.println(all);
}
构建条件匹配器
@Test
public void queryWhereRegexTest() {
  Customer customer = new Customer();
  customer.setName("张三");
  customer.setAddress("shanghai");
  // 忽略指定的属性
  Iterable<Customer> ignore = queryByExampleRepositories.findAll(Example.of(customer, ExampleMatcher                                                                           .matching().withIgnorePaths("address")));
  System.out.println(ignore);
}

其中address是实体类Customer类中的属性

Specifications查询方式

Query By Example的查询方式只支持字符串且无法对查询条件做出><<>的限制,所以需要使用Specifications的查询方式来实现,但Specifications的查询方式不能使用分组、聚合函数。

public interface SpecificationsRepositories extends CrudRepository<Customer, Long>,
        JpaSpecificationExecutor<Customer> {
}

使用Specifications的查询方式需要继承JpaSpecificationExecutor接口,如下案例查询addressBEIJINid在3,20,18的数据

@Test
public void queryTest() {
  Iterable<Customer> all = specificationsRepositories.findAll((Specification<Customer>)
    (root, query, criteriaBuilder) -> criteriaBuilder                                                             .and(criteriaBuilder.equal(root.get("id"), "BEIJIN"),                                                                  criteriaBuilder.in(root.get("address")).value(3L).value(20L).value(18L)));
  all.forEach(System.out::println);
}

参数说明

(root, query, criteriaBuilder)中的root相当于from Customer可以获取查询的列,criteriaBuilder设置各种查询条件如<>或者in等操作、query设置各种组合条件如order bywhere等。

QueryDSL

QueryDSL是基于ORM框架或者SQL平台上的一个通用查询框架,借助QueryDSL可以在任何支持的ORM框架或者SQL平台上以通用的API方式构建查询。

引入依赖
<dependency>
  <groupId>com.querydsl</groupId>
  <artifactId>querydsl-jpa</artifactId>
  <version>4.4.0</version>
</dependency>
安装插件
<build>
  <plugins>
    <plugin>
      <groupId>com.mysema.maven</groupId>
      <artifactId>apt-maven-plugin</artifactId>
      <version>1.1.3</version>
      <dependencies>
        <dependency>
          <groupId>com.querydsl</groupId>
          <artifactId>querydsl-apt</artifactId>
          <version>4.4.0</version>
        </dependency>
      </dependencies>
      <executions>
        <execution>
          <phase>generate-sources</phase>
          <goals>
            <goal>process</goal>
          </goals>
          <configuration>
            <outputDirectory>target/generated-sources/queris</outputDirectory>
            <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
            <logOnlyOnError>true</logOnlyOnError>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

导入依赖并安装好插件后使用maven compile一下

将编译生成的QClass进行source

在这里插入图片描述

在这里插入图片描述

这样子就可以引用编译生成的QClass

查询案例
public interface QueryDSLRepositories extends QuerydslPredicateExecutor<Customer>, CrudRepository<Customer, Long> {
}

注意必须继承QuerydslPredicateExecutorCrudRepositoryCrudRepository的子类

@ContextConfiguration(classes = SpringDataJpaConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class QueryDSLTest {
    @Autowired
    private QueryDSLRepositories queryDSLRepositories;
   	@Test
    public void queryTest() {
        QCustomer customer = QCustomer.customer;
        Iterable<Customer> all = queryDSLRepositories.findAll(customer.id.in(1L, 18L, 20L)
                .and(customer.id.gt(5L))
                .and(customer.address.eq("BEIJIN")));
        all.forEach(System.out::println);
    }
}

相当于sql语句SELECT * FROM customer WHERE id IN(1, 18, 20) AND id > 5 AND address = 'BEIJIN'

自定义列查询或者分组查询
@ContextConfiguration(classes = SpringDataJpaConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class QueryDSLTest {
    @PersistenceContext // 保证线程安全
    private EntityManager entityManager;
    @Test
    public void groupQueryTest() {
        JPAQueryFactory factory = new JPAQueryFactory(entityManager);
        QCustomer customer = QCustomer.customer;
        JPAQuery<Tuple> query = factory.select(customer.id, customer.name)
                .from(customer)
                .where(customer.id.eq(18L))
                .orderBy(customer.id.desc());
        List<Tuple> fetch = query.fetch();
        for (Tuple tuple : fetch) {
            System.out.println(tuple.get(customer.id));
            System.out.println(tuple.get(customer.name));
        }
    }
}

相当于SQL语句SELECT id, name FROM customer WHERE id = 18 ORDER BY id DESC

多表关联

一对一

配置关联关系,实体类Customer配置单向关联

@Data
@Entity
@Table(name = "cst_customer")
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cust_id")
    private Long id;
    @Column(name = "cust_address")
    private String address;
    @Column(name = "cust_name")
    private String name;
    // 单向关联
    @OneToOne(cascade = CascadeType.PERSIST)
    @JoinColumn(name = "account_id")
    private Account account;
}

注解@OneToOnecascade参数说明:

  1. CascadeType.PERSIST
    级联持久化(保存)操作:持久保存拥有方实体时,也会持久保存该实体的所有相关数据。这个属性就是造成上面问题的关键。当你保存一天条数据时,所有的关联数据都会进行保存,无论数据库里面有没有,但有时候我们是需要这样的级联操作的。

  2. CascadeType.REMOVE
    级联删除操作:删除当前实体时,与它有映射关系的实体也会跟着被删除。

  3. CascadeType.DETACH
    级联脱管/游离操作:如果你要删除一个实体,但是它有外键无法删除,你就需要这个级联权限了。它会撤销所有相关的外键关联。

  4. CascadeType.REFRESH
    级联刷新操作:假设场景 有一个订单,订单里面关联了许多商品,这个订单可以被很多人操作,那么这个时候A对此订单和关联的商品进行了修改,与此同时,B也进行了相同的操作,但是B先一步比A保存了数据,那么当A保存数据的时候,就需要先刷新订单信息及关联的商品信息后,再将订单及商品保存。

  5. CascadeType.MERGE
    级联更新(合并)操作:当Student中的数据改变,会相应地更新Course中的数据。

  6. CascadeType.ALL
    清晰明确,拥有以上所有级联操作权限。

fetch参数说明:

fetch参数的作用类似于设计模式中的单例模式,默认为EAGER饿汉模式,可以设置为LAZY懒汉模式,当设置为LAZY的时候只有在使用到对应的实体类的时候就会加载执行查询

示例在Customer类中对应的属性添加如下代码

@OneToOne(cascade = CascadeType.PERSIST,fetch = FetchType.LAZY)
@JoinColumn(name = "account_id")
private Account account;

查询案例

@Test
@Transactional(readOnly = true)
public void queryTest() {
  Optional<Customer> byId = repositories.findById(1L);
  System.out.println(byId.get());
}

需要添加@Transactional注解开启事务,这是由于通过repositories接口来调用查询方法执行完后,session会立即关闭,一旦session关闭了就不能进行查询了,所以在fetch = FetchType.LAZY的情况下执行完repositories.findById(1L)后会关闭session导致在执行byId.get()调用查询会报错。而事务是在整个方法执行完后关闭session

实体类Account配置如下

@Data
@Entity
@Table(name = "cst_account")
public class Account {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;
}

测试代码

public interface CustomerRepositories extends PagingAndSortingRepository<Customer, Long> {
}
@ContextConfiguration(classes = SpringDataJpaConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class OneToOneTest {
    @Autowired
    private CustomerRepositories repositories;
    @Test
    public void saveTest() {
        Account account = new Account();
        account.setUsername("xushu");
        Customer customer = new Customer();
        customer.setName("徐庶");
        customer.setAccount(account);
        repositories.save(customer);
    }
}

如果出现Field 'id' doesn't have a default value的错误,请检查对应的表的主键Id是否设置为自增

一对多

表设计如下

在这里插入图片描述

实体类Message配置如下:

@Data
@Entity
@Table(name = "cst_message")
public class Message {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String info;

    public Message(String info) {
        this.info = info;
    }

    public Message() {
    }
}

必须需要拥有无参构造函数

实体类Customer配置如下:

@Data
@Entity
@Table(name = "cst_customer")
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cust_id")
    private Long id;
    @Column(name = "cust_address")
    private String address;
    @Column(name = "cust_name")
    private String name;
    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "customer_id")
    private List<Message> messages;
}

注意当查询一对多的数据表的时候,Spring Data JPA默认使用的是懒加载,所以需要使用@Transactional开启事务来解决运行时出现no-session的错误

示例代码

@Test
@Transactional
public void testQuery() {
  Optional<Customer> byId = repositories.findById(7L);
  System.out.println(byId.get());
}

或者在实体类中的Customer中添加@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)注解,解决出现no-session错误,如下:

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name = "customer_id")
private List<Message> messages;
一对多删除操作注意事项

如果在Customer实体类中@OneToManycascade = CascadeType.PERSIST这会只删除Customer表中的信息不会删除Message表中的信息

多对一

实例代码:

Message实体类中进行绑定

@Data
@Entity
@Table(name = "cst_message")
public class Message {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String info;
    @ManyToOne(cascade = CascadeType.PERSIST)
    @JoinColumn(name = "customer_id")
    private Customer customer;

    public Message(String info) {
        this.info = info;
    }

    public Message(Customer customer, String info) {
        this.customer = customer;
        this.info = info;
    }
    public Message() {
    }
}

使用注解@ManyToOne(cascade = CascadeType.PERSIST)以及@JoinColumn(name = "customer_id")

多对多

多对多表的关系如下:

在这里插入图片描述

注意:在cst_customer_role表中不需要任何主键

Customer类配置如下:

@Data
@Entity
@Table(name = "cst_customer")
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cust_id")
    private Long id;
    @Column(name = "cust_address")
    private String address;
    @Column(name = "cust_name")
    private String name;
    @OneToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
    @JoinColumn(name = "customer_id")
    private List<Message> messages;
    public Customer(String name) {
        this.name = name;
    }
    public Customer() {
    }

    /**
     * 单向的多对多
     * 中间表需要通过@JoinTable来维护外键
     * name 指定中间表名称
     * joinColumn 设置本表的外键名称
     * inverseJoinColumns 关联表的外键名称
     */
    @ManyToMany(cascade = CascadeType.PERSIST)
    @JoinTable(name = "cst_customer_role",
            joinColumns = {@JoinColumn(name = "cust_id")},
            inverseJoinColumns = {@JoinColumn(name = "role_id")}
    )
    private List<Role> roles;

    @Override
    public String toString() {
        return "Customer{" +
                "id=" + id +
                ", address='" + address + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}

Role类信息如下:

@Data
@Entity
@Table(name = "cst_role")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "role_id")
    private Long id;
    @Column(name = "role_name")
    private String name;

    public Role(String name) {
        this.name = name;
    }
    public Role() {
    }
}

实例代码

/***
* 如果保存的关联对象的数据,希望使用数据库已有的数据,
* 那么就需要从数据库中查询出来。否则会抛出异常
* 同事需要使用@Transactional来解决报错问题,
* 单元测试需要使用@Commit解决事务持久化问题
* @return void
* @author Tang
* @date 2023/12/6 23:25:17
*/
@Test
public void testSave() {
  Customer customer = new Customer();
  customer.setName("zhangshang");
  List<Role> roles =  new ArrayList<Role>() {{
    add(new Role("超级管理员"));
    add(new Role("商品管理员"));
  }};
  customer.setRoles(roles);

  customerRepository.save(customer);
}

其中如果出现了Field 'xxx_id' doesn't have a default value,是在数据库方没有设置主键自增所导致的

审计

为了数据库中的数据方便溯源所以经常需要在数据库中添加创建人修改人修改时间创建时间。由于经常频繁的操作所以Spring data jpa使用审计功能来解决这个问题,操作如下:

由于Spring Data JPA的审计功能需要依赖Spring AOP相关依赖,否则会抛出Could not configure Spring Data JPA auditing-feature because spring-aspects.jar is not on the classpath!异常

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>5.3.28</version>
</dependency>

在对应的实体类添加如下字段

    // 创建人
    @CreatedBy
    private String creator;
    // 修改人
    @LastModifiedBy
    private String modifier;
    // 创建时间 @Temporal 注解指定时间类型
    @Temporal(TemporalType.TIMESTAMP)
    @CreatedDate
    private Date dateCreated = new Date();
    // 修改时间
    @Temporal(TemporalType.TIMESTAMP)
    @LastModifiedDate
    private Date dateModified = new Date();

同时在实体类上添加@EntityListeners(AuditingEntityListener.class)用于监听审计监听对应的实体类

在配置类上配置如下Bean信息

/***
* 配置审计功能,AuditorAware中泛型的类型与
* 后端获取到的用户类型一直可以是String可以是User对象等
* @return org.springframework.data.domain.AuditorAware<java.lang.String>
* @author Tang
* @date 2023/12/7 23:19:17
*/
@Bean
public AuditorAware<String> auditorAware() {
  return () -> Optional.of("创建人");
}

使用@EnableJpaAuditing开启审计功能

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1301459.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于Java SSM框架实现个性化影片推荐系统项目【项目源码+论文说明】

基于java的SSM框架实现个性化影片推荐系统演示 摘要 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;个性化影片推荐系统当然也不能排除在外。个性化影片推荐系统是以实际运用…

【Citespace】从Citespace开始的引文可视化分析

CiteSpace 译“引文空间”&#xff0c;是一款着眼于分析科学分析中蕴含的潜在知识&#xff0c;是在科学计量学、数据可视化背景下逐渐发展起来的引文可视化分析软件。由于是通过可视化的手段来呈现科学知识的结构、规律和分布情况&#xff0c;因此也将通过此类方法分析得到的可…

经典目标检测YOLO系列(一)引言_目标检测架构

经典目标检测YOLO系列(一)引言_目标检测架构 一个常见的目标检测网络&#xff0c;其本身往往可以分为一下三大块&#xff1a; Backbone network&#xff0c;即主干网络&#xff0c;是目标检测网络最为核心的部分&#xff0c;backbone选择的好坏&#xff0c;对检测性能影响是十…

Jol-分析Java对象的内存布局

Jol-分析Java对象的内存布局 Open JDK提供的JOL(Java Object Layout)工具为我们方便分析、了解一个Java对象在内存当中的具体布局情况。本文实验环境为64位HotSpot虚拟机。 Java对象的内存布局 Java的实例对象、数组对象在内存中的组成包括&#xff1a;对象头、实例数据和内存…

一键优化工具,十分不错的win7、win10系统优化的工具,可以帮助用户轻松快速优化系统,供大家学习研究参考~

主要功能 01、禁用索引服务 02、禁止window发送错误报告 03、禁用"最近使用的项目” 04、关闭Windows Defender 05、关闭防火墙 06、检查更新而不自动下载更新 07、启动电源计划“高性能” 08、调整电源选项 09、禁用休眠(删除休眠文件) 10、开启快速启动 11、…

【lesson3】数据库表的操作

文章目录 创建修改修改表名增加表类型修改表的某一类型的类型修改表某一类型的类型名 删除删除表的某一列删除表 查看查看表信息查看表内容 创建 建表指令&#xff1a; 查看是否建表成功&#xff1a; 查看表的具体信息&#xff1a; 修改 修改表名 法一&#xff1a;修改…

yolov5目标检测

一、安装 1.源码下载 git clone git://github.com/ultralytics/yolov5.git cd yolov5 2.环境配置 conda create -n yolov5 python3.8 conda activate yolov5 nvcc -V查看cuda版本 pytorch官网下载对应版本&#xff0c;例如当cuda版本为11.6 pip install torch1.13.1cu…

阿里云服务器租用价格分享,阿里云服务器热门配置最新活动价格汇总

在我们购买阿里云服务器的时候&#xff0c;1核2G、2核2G、2核4G、2核8G、4核8G、8核16G、8核32G等配置属于用户购买最多的热门配置&#xff0c;1核2G、2核2G、2核4G这些配置低一点的云服务器基本上能够满足绝大部分个人建站和普通企业用户建站需求&#xff0c;而4核8G、8核16G、…

Vue之模板语法

模板语法有两大类&#xff1a; 1.插值语法 2.指令语法 让我为大家介绍一下吧&#xff01; 一、插值语法 功能:用于解析标签体内容。 写法: {{xxx}}&#xff0c;xxx是js表达式&#xff0c;且可以直接读取到data中的所有属性。 举个例子&#xff1a; <!DOCTYPE html> &l…

六级高频词汇3

目录 单词 参考链接 单词 400. nonsense n. 胡说&#xff0c;冒失的行动 401. nuclear a. 核子的&#xff0c;核能的 402. nucleus n. 核 403. retail n. /v. /ad. 零售 404. retain vt. 保留&#xff0c;保持 405. restrict vt. 限制&#xff0c;约束 406. sponsor n. …

LinuxBasicsForHackers笔记 -- BASH 脚本

你的第一个脚本&#xff1a;“你好&#xff0c;黑客崛起&#xff01;” 首先&#xff0c;您需要告诉操作系统您要为脚本使用哪个解释器。 为此&#xff0c;请输入 shebang&#xff0c;它是井号和感叹号的组合&#xff0c;如下所示&#xff1a;#! 然后&#xff0c;在 shebang …

“我“的测试之路,从初级测试到测试开发,往后前景...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、测试工程师的现…

C++ //习题2.5 请写出下列表达式的值。

C程序设计 &#xff08;第三版&#xff09; 谭浩强 习题2.5 习题2.5 请写出下列表达式的值。 (1) 3.5 * 3 2 * 7 - ‘a’ (2) 26 / 3 34 % 3 2.5 (3) 45 / 2 (int)3.14159 / 2 (4) a b (c a 6) 设a的初值为3 (5) a 3 * 5, a b 3 * 2 (6) (int)(a 6.5) % 2 …

DevEco Studio 3.1IDE环境配置(HarmonyOS 3.1)

DevEco Studio 3.1IDE环境配置&#xff08;HarmonyOS 3.1&#xff09; 一、安装环境 操作系统: Windows 10 专业版 IDE:DevEco Studio 3.1 SDK:HarmonyOS 3.1 二、环境安装 IDE下载地址&#xff1a;HUAWEI DevEco Studio和SDK下载和升级 | HarmonyOS开发者 IDE的安装就是…

家具制造ERP软件包含哪些功能?家具制造业ERP系统哪个好

不同的家具有不同的用料、品质、制造工时、营销渠道等&#xff0c;而有些家具制造企业采用传统的管理方式在处理物料BOM、生产实际成本核算、库存盘点、供应商选择、班组计件核对、生产领用以及物料追溯等方面存在不少提升空间。 与此同时也有很多的皮具制造企业借助ERP软件优…

探索HarmonyOS_开发软件安装

随着华为推出HarmonyOS NEXT 宣布将要全面启用鸿蒙原声应用&#xff0c;不在兼容安卓应用&#xff0c; 现在开始探索鸿蒙原生应用的开发。 HarmonyOS应用开发官网 - 华为HarmonyOS打造全场景新服务 鸿蒙官网 开发软件肯定要从这里下载 第一个为微软系统(windows)&#xff0c;第…

GLAB | CCNA+HCIA=融合课-最新开课通知

敲重点! 12月17日 CCNAHCIA 周日开课啦&#xff01; CCNA&#xff08;Cisco Certified Network Associate&#xff09;认证是Cisco售后工程师认证体系的入门认证&#xff0c;也是Cisco各项认证中级别最低的技术认证通过CCNA认证可证明你已掌握网络的基本知识&#xff0c;并能…

docker 一键寻找容器在服务器存储位置

docker ps -a找到容器id/容器名称 docker inspect 容器id/容器名称 | grep UpperDir找出该容器在物理机的位置 inspect作用:查看docker详细信息 cd到UpperDir所指向的地址&#xff0c;找到配置文件并修改,到这后,这个位置和你用exec命令进入容器内看到文件是一致的

盛域宏数合伙人张天:AI时代,数字化要以AI重构

大数据产业创新服务媒体 ——聚焦数据 改变商业 在这个飞速发展的科技时代&#xff0c;数字化已经深刻地改变了我们的生活和商业方式。信息技术的迅猛发展使得数据成为现代社会最宝贵的资源之一。数字化已经不再是可选项&#xff0c;而是企业持续发展的必由之路。背靠着数据的…

【LeetCode:1631. 最小体力消耗路径 | BFS + 二分】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…