Spring Data JPA

Spring Data JPA 与 JPA和hibernate之间的关系

JPA是一套规范,内部是有接口和抽象类组成的。hibernate是一套成熟的ORM框架,而且Hibernate实现了JPA规范,所以也可以称hibernate为JPA的一种实现方式,我们使用JPA的API编程,意味着站在更高的角度上看待问题(面向接口编程)

Spring Data JPA是Spring提供的一套对JPA操作更加高级的封装,是在JPA规范下的专门用来进行数据持久化的解决方案。

Spring Data JPA的入门案例

1. 引入Spring Data JPA坐标

<properties>
    <spring.version>4.2.4.RELEASE</spring.version>
    <hibernate.version>5.0.7.Final</hibernate.version>
    <slf4j.version>1.6.6</slf4j.version>
    <log4j.version>1.2.12</log4j.version>
    <c3p0.version>0.9.1.2</c3p0.version>
    <mysql.version>8.0.16</mysql.version>
</properties>

<dependencies>
    <!-- junit单元测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.9</version>
        <scope>test</scope>
    </dependency>

    <!-- spring beg -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.6.8</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-orm</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <!-- spring end -->

    <!-- hibernate beg -->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>${hibernate.version}</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>${hibernate.version}</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>5.2.1.Final</version>
    </dependency>
    <!-- hibernate end -->

    <!-- c3p0 beg -->
    <dependency>
        <groupId>c3p0</groupId>
        <artifactId>c3p0</artifactId>
        <version>${c3p0.version}</version>
    </dependency>
    <!-- c3p0 end -->

    <!-- log end -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>${log4j.version}</version>
    </dependency>

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>${slf4j.version}</version>
    </dependency>

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>${slf4j.version}</version>
    </dependency>
    <!-- log end -->


    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${mysql.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-jpa</artifactId>
        <version>1.9.0.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <!-- el beg 使用spring data jpa 必须引入 -->
    <dependency>
        <groupId>javax.el</groupId>
        <artifactId>javax.el-api</artifactId>
        <version>2.2.4</version>
    </dependency>

    <dependency>
        <groupId>org.glassfish.web</groupId>
        <artifactId>javax.el</artifactId>
        <version>2.2.4</version>
    </dependency>
    <!-- el end -->
</dependencies>

2. 整合Spring Data JPA与Spring,配置applicationContext.xml

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
		http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
		http://www.springframework.org/schema/data/jpa
		http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

    <!--创建entityManagerFactory对象交给Spring管理-->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <!--配置数据源-->
        <property name="dataSource" ref="dataSource"/>
        <!--配置扫描的包(实体类所在的包)-->
        <property name="packagesToScan" value="com.xxx.proj.entity"/>
        <!--jpa的实现厂家-->
        <property name="persistenceProvider">
            <bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>
        </property>
        <!--JPA的供应商适配器(hibernate的配置)-->
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <!--配置是否自动创建数据库表-->
                <!--true表示没有表时自动创建,有表时不创建。false表示不创建-->
                <property name="generateDdl" value="true"/>
                <!--指定数据库类型-->
                <property name="database" value="MYSQL"/>
                <!--配置数据库方言,支持特有语法-->
                <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"/>
                <!--配置是否显示sql-->
                <property name="showSql" value="true"/>
            </bean>
        </property>
        <!--配置jpa方言,高级特性-->
        <property name="jpaDialect">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
        </property>
    </bean>
    <!--创建dataSource对象,配置数据库连接池-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/jpa"/>
        <property name="user" value="root"/>
        <property name="password" value="admin123"/>
    </bean>
    <!-- 事务管理器  -->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>
    <!-- 整合spring-data-jpa -->
    <jpa:repositories base-package="com.xxx.proj.dao"
                      transaction-manager-ref="transactionManager"
                      entity-manager-factory-ref="entityManagerFactory"></jpa:repositories>
    <!--配置包扫描-->
    <context:component-scan base-package="com.xxx.proj"></context:component-scan>
    
</beans>

3. 创建客户的实体类、编写实体类和数据库表的映射配置:

package com.xxx.proj.entity;
import javax.persistence.*;
import java.io.Serializable;

/**
 * TODO
 *
 * @author wuqirui
 * @since 2021/11/29
 */
@Entity
@Table(name = "cst_customer")
public class Customer implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    //columnDefinition:指定创建表时一些SQL语句,字段类型和长度为必填。一般用于通过Entity生成表定义时使用。若不指定创建表时SQL语句,则主键默认bigint(20),其余默认varchar(255)
    @Column(name = "cust_id",columnDefinition = "bigint(32) NOT NULL COMMENT '客户编号(主键)'")
    private Long custId;

    @Column(name = "cust_name",columnDefinition = "varchar(32) NOT NULL COMMENT '客户名称(公司名称)'")
    private String custName;

    @Column(name = "cust_source",columnDefinition = "varchar(32) DEFAULT NULL COMMENT '客户信息来源'")
    private String custSource;

    @Column(name="cust_industry",columnDefinition = "varchar(32) DEFAULT NULL COMMENT '客户所属行业'")
    private String custIndustry;

    @Column(name = "cust_level",columnDefinition = "varchar(32) DEFAULT NULL COMMENT '客户级别'")
    private String custLevel;

    @Column(name = "cust_address",columnDefinition = "varchar(32) DEFAULT NULL COMMENT '客户联系地址'")
    private String custAddress;

    @Column(name="cust_phone",columnDefinition = "varchar(32) DEFAULT NULL COMMENT '客户联系电话'")
    private String custPhone;

    public Long getCustId() {
        return custId;
    }

    public void setCustId(Long custId) {
        this.custId = custId;
    }

    public String getCustName() {
        return custName;
    }

    public void setCustName(String custName) {
        this.custName = custName;
    }

    public String getCustSource() {
        return custSource;
    }

    public void setCustSource(String custSource) {
        this.custSource = custSource;
    }

    public String getCustIndustry() {
        return custIndustry;
    }

    public void setCustIndustry(String custIndustry) {
        this.custIndustry = custIndustry;
    }

    public String getCustLevel() {
        return custLevel;
    }

    public void setCustLevel(String custLevel) {
        this.custLevel = custLevel;
    }

    public String getCustAddress() {
        return custAddress;
    }

    public void setCustAddress(String custAddress) {
        this.custAddress = custAddress;
    }

    public String getCustPhone() {
        return custPhone;
    }

    public void setCustPhone(String custPhone) {
        this.custPhone = custPhone;
    }

    @Override
    public String toString() {
        return "Customer{" +
                "custId=" + custId +
                ", custName='" + custName + '\'' +
                ", custSource='" + custSource + '\'' +
                ", custIndustry='" + custIndustry + '\'' +
                ", custLevel='" + custLevel + '\'' +
                ", custAddress='" + custAddress + '\'' +
                ", custPhone='" + custPhone + '\'' +
                '}';
    }
}

4. 编写符合Spring Data JPA规范的Dao层接口

 创建一个Dao层接口,并实现JpaRepository和JpaSpecificationExecutor,提供相应的泛型。

package com.xxx.proj.dao;

import com.xxx.proj.entity.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * TODO
 *
 * @author wuqirui
 * @since 2021/12/11
 */
public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {

}

 5. 完成基本增删改查操作(使用JpaRepository接口和CrudRepository接口方法)

package com.xxx.proj.test;

import com.xxx.proj.dao.CustomerDao;
import com.xxx.proj.entity.Customer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * TODO
 *
 * @author wuqirui
 * @since 2021/12/11
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class CustomerDaoTest {
    @Autowired
    CustomerDao customerDao;

    @Test
    public void testFindOne() {
        //CrudRepository接口
        Customer customer = customerDao.findOne(1L);
        System.out.println(customer);
    }
    @Test
    //根据是否传递ID判断是新增还是修改,(若传递ID,根据查询数据库ID是否存在判断是新增还是修改)
    public void testSave() {
        Customer customer = new Customer();
        customer.setCustAddress("上海");
        customer.setCustIndustry("金融");
        customer.setCustLevel("VIP");
        customer.setCustName("中国建设银行");
        customer.setCustPhone("010-84389340");
        customer.setCustSource("网络");
        //CrudRepository接口
        Customer save = customerDao.save(customer);
        System.out.println(save);
    }
    
    @Test
    public void testDelete() {
        //CrudRepository接口
        customerDao.delete(1L);
    }

    @Test
    public void testFindAll() {
        //JpaRepository接口
        List<Customer> list = customerDao.findAll();
        for (Customer customer : list) {
            System.out.println(customer);
        }
    }

    @Test
    public void testCount() {
        //CrudRepository接口
        long count = customerDao.count();
        System.out.println(count);
    }

    @Test
    public void testExit() {
        //CrudRepository接口
        boolean b = customerDao.exists(1L);
        System.out.println(b);
    }

    @Test
    @Transactional
    public void testGetOne() {
        //JpaRepository接口
        //封装了jpa的getRefrence()延迟加载,使用这个方法必须配置@Transactional注解
        Customer customer = customerDao.getOne(1L);
        System.out.println(customer);
    }
}

 Spring Data JPA接口分析

在客户的案例中,我们发现在自定义的CustomerDao中,并没有提供任何方法就可以使用中的很多方法,对于我们自定义的Dao接口,由于继承了JpaRepository和JpaSpecificationExecutor,所以我们可以使用这两个接口的所有方法。

 Spring Data JPA接口的结构图

 

 Spring Data JPA的实现原理

我们通过@Autowired注入的CustomerDao对象,本质上是通过JdkDynamicAopProxy生成的一个代理对象,当程序执行的时候,会通过JdkDynamicAopProxy的invoke方法,对CustomerDao对象生成动态代理对象SimpleJpaRepository,SimpleJpaRepository的方法中使用EntityManager(JPA的原生方式),所有Spring Data Jpa只是对标准JPA进行了进一步的封装,简化了Dao层代码的开发。 

分页和排序查询(PagingAndSortingRepository接口) 

@Test
    public void testSort() {
        //Sort.Direction.ASC:正序
        //Sort.Direction.DESC:倒叙
        Sort sort = new Sort(Sort.Direction.DESC,"custId");
        List<Customer> list = customerDao.findAll(sort);
        System.out.println(list);
    }

//Page<T>
//获取总页数:int getTotalPages();
//获取总记录数:long getTotalElements();
//获取列表数据:List<T> getContent();
    @Test
    public void testPage() {
        //PageRequest(int page, int size)
        //page:当前页码
        //size: 页容量
        Pageable pageable = new PageRequest(0,2);
        Page<Customer> list = customerDao.findAll(pageable);
        System.out.println(list.getContent());
    }

JPQL方式查询 

使用Spring Data JPA提供的查询方法已经可以解决大部分的应用场景,但是对于某些业务来说,我们还需要灵活的构造查询条件,这时就可以使用@Query注解,结合JPQL的语句方式完成查询

@Query 注解,只需在方法上面标注该注解,同时提供一个JPQL查询语句即可

package com.xxx.proj.dao;

import com.xxx.proj.entity.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * TODO
 *
 * @author wuqirui@yihuacomputer.com
 * @since 2021/12/11
 */
public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {
    /**
     * 根据客户名查询客户
     * Jpql
     */
    //可以省略select c,若不想省略不能直接使用*,只能用c代替*,也可以select c.属性来获取表中某一字段
    //@Query(value = "from Customer c where c.custName = ?")
    @Query(value = "select c from Customer c where c.custName = ?")
    public Customer findByCustNameJpql(String custName);

    /**
     * 根据客户名和客户id查询客户
     * Jpql
     */
    //可以通过?数字来选择参数
    @Query(value = "select c from Customer c where c.custName = ?2 and c.custId = ?1" )
    public Customer findByCustNameAndIdJpql(Long custId,String custName);

    /**
     * 根据id更新,客户名称
     * Jpql
     */
    @Query(value = "update Customer set custName = ?2 where custId = ?1")
    @Modifying//告诉jpa这是不是查询操作
    @Transactional//事务默认readOnly=false
    public void updateCustomerJpql(Long custId, String custName);

}

原生SQL方式进行查询 

Spring Data JPA同样也支持sql语句的查询,通过@Query注解中nativeQuery来选择是SQL还是JPQL语句。

    /**
     * 查询全部客户
     * sql
     */
    @Query(value = "select * from cst_customer",nativeQuery = true)
    public List<Customer> findAllSql();

    /**
     * 模糊查询
     * sql
     */
    @Query(value = "select * from cst_customer where cust_name like ?",nativeQuery = true)
    public List<Customer> findLikeSql(String custName);

方法命名规则查询

根据方法的名字,就能创建查询。只需要按照Spring Data JPA提供的方法命名规则定义方法的名称,就可以完成查询工作。Spring Data JPA在程序执行的时候会根据方法名称进行解析,并自动生成查询语句进行查询。

按照Spring Data JPA 定义的规则,查询方法以findBy开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写。框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进行解析,注意这里参数顺序必须跟方法名参数保持一致。

     /**
     * 方法命名规则查询:基本查询
     */
    public Customer findByCustName(String custName);

    /**
     *方法命名规则查询:模糊查询
     */
    public List<Customer> findByCustNameLike(String custName);

    /**
     * 方法命名规则查询:多条件查询
     */
    public Customer findByCustNameLikeAndCustIndustry(String custName,String custIndustry);

Specifications动态查询(JpaSpecificationExecutor接口)

我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句,在Spring Data JPA中可以通过JpaSpecificationExecutor接口查询。相比JPQL,其优势是类型安全,更加的面向对象。

/**
     * 使用动态查询完成单个对象查询
     * root:Root接口,代表查询的根对象,可以通过root获取实体中的属性
     * criteriaQuery:代表一个顶层查询对象,用来自定义查询
     * criteriaBuilder:用来构建查询,此对象里有很多条件方法
     */
    @Test
    public void testSpecFindOne() {
        Specification<Customer> specification = new Specification<Customer>() {
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                Path<Object> custId = root.get("custId");
                Predicate predicate = criteriaBuilder.equal(custId, 1L);
                return predicate;
            }
        };
        Customer customer = customerDao.findOne(specification);
        System.out.println(customer);
    }

    /**
     * 使用动态查询完成多条件拼接
     */
    @Test
    public void testSpecFind() {
        Specification<Customer> specification = new Specification<Customer>() {
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                Path<Object> custName = root.get("custName");
                Path<Object> custIndustry = root.get("custIndustry");
                Predicate predicate1 = criteriaBuilder.equal(custName, "阿里巴巴");
                Predicate predicate2 = criteriaBuilder.equal(custIndustry, "互联网");
                Predicate and = criteriaBuilder.and(predicate1, predicate2);
                return and;
            }
        };
        Customer customer = customerDao.findOne(specification);
        System.out.println(customer);
    }

    /**
     * 使用动态查询完成模糊匹配查询列表
     * equal:直接得到path对象(属性),然后进行比较即可
     * gt、lt、ge、le、like:得到path对象,根据path指定比较的参数类型再去比较
     * 指定参数类型,path.as(类型的字节码对象)
       equle	            filed = value
       gt(greaterThan )	filed > value
       lt(lessThan )	    filed < value
       ge(greaterThanOrEqualTo )	filed >= value
       le( lessThanOrEqualTo)	    filed <= value
       notEqule	            filed != value
       like	                filed like value
       notLike	            filed not like value
     */
    @Test
    public void testSpecFindLike() {
        Specification<Customer> specification = new Specification<Customer>() {
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                Path<Object> custName = root.get("custName");
                Predicate predicate = criteriaBuilder.like(custName.as(String.class), "阿里%");
                return predicate;
            }
        };
        List<Customer> list = customerDao.findAll(specification);
        System.out.println(list);
    }

注意:关联关系在生产环境一般不推荐使用,一般使用逻辑关联               

Spring Data Jpa多表:一对多

 1. pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xxx.proj</groupId>
    <artifactId>jpa-day3-onetomany-again</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <spring.version>4.2.4.RELEASE</spring.version>
        <hibernate.version>5.0.7.Final</hibernate.version>
        <slf4j.version>1.6.6</slf4j.version>
        <log4j.version>1.2.12</log4j.version>
        <c3p0.version>0.9.1.2</c3p0.version>
        <mysql.version>8.0.16</mysql.version>
    </properties>

    <dependencies>
        <!-- junit单元测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.9</version>
            <scope>test</scope>
        </dependency>

        <!-- spring beg -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.6.8</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- spring end -->

        <!-- hibernate beg -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>5.2.1.Final</version>
        </dependency>
        <!-- hibernate end -->

        <!-- c3p0 beg -->
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>${c3p0.version}</version>
        </dependency>
        <!-- c3p0 end -->

        <!-- log end -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <!-- log end -->


        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>1.9.0.RELEASE</version>
            <!--<version>2.1.19.RELEASE</version>-->
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- el beg 使用spring data jpa 必须引入 -->
        <dependency>
            <groupId>javax.el</groupId>
            <artifactId>javax.el-api</artifactId>
            <version>2.2.4</version>
        </dependency>

        <dependency>
            <groupId>org.glassfish.web</groupId>
            <artifactId>javax.el</artifactId>
            <version>2.2.4</version>
        </dependency>
        <!-- el end -->

    </dependencies>
</project>

2. applicationContext.xml 

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
		http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
		http://www.springframework.org/schema/data/jpa
		http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

    <!--创建entityManagerFactory对象交给Spring管理-->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <!--配置数据源-->
        <property name="dataSource" ref="dataSource"/>
        <!--配置扫描的包(实体类所在的包)-->
        <property name="packagesToScan" value="com.xxx.proj.entity"/>
        <!--jpa的实现厂家-->
        <property name="persistenceProvider">
            <bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>
        </property>
        <!--JPA的供应商适配器(hibernate的配置)-->
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <!--配置是否自动创建数据库表,spring-data-jpa提供-->
                <property name="generateDdl" value="false"/>
                <!--指定数据库类型-->
                <property name="database" value="MYSQL"/>
                <!--配置数据库方言,支持特有语法-->
                <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"/>
                <!--配置是否显示sql-->
                <property name="showSql" value="true"/>
            </bean>
        </property>
        <!--配置jpa方言,高级特性-->
        <property name="jpaDialect">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
        </property>
        <!--注入jpa的配置信息
            加载jpa的基本配置信息和jpa实现方式(hibernate的配置)
            hibernate.hbm2ddl.auto:自动创建数据库(比generateDdl更精确,并且优先级高于generateDdl,若不自动建表auto和generateDdl都要设置为不建表,若自动建表只需要设置其中一个)
            create:每次都创建数据库表
            update:有表不会重新创建,没有表会重新创建 -->
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
            </props>
        </property>
    </bean>
    <!--创建dataSource对象,配置数据库连接池-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/jpa"/>
        <property name="user" value="root"/>
        <property name="password" value="admin123"/>
    </bean>
    <!-- 事务管理器  -->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>
    <!-- 整合spring-data-jpa -->
    <jpa:repositories base-package="com.xxx.proj.dao"
                      transaction-manager-ref="transactionManager"
                      entity-manager-factory-ref="entityManagerFactory"></jpa:repositories>
    <!--配置包扫描-->
    <context:component-scan base-package="com.xxx.proj"></context:component-scan>

</beans>

3. Entity

  Customer 

package com.xxx.proj.entity;
import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

/**
 * TODO
 *
 * @author wuqirui@yihuacomputer.com
 * @since 2021/11/29
 */
@Entity
@Table(name = "cst_customer")
public class Customer implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cust_id",columnDefinition = "bigint(32) NOT NULL COMMENT '客户编号(主键)'")
    private Long custId;

    @Column(name = "cust_name",columnDefinition = "varchar(32) NOT NULL COMMENT '客户名称(公司名称)'")
    private String custName;

    @Column(name = "cust_source",columnDefinition = "varchar(32) DEFAULT NULL COMMENT '客户信息来源'")
    private String custSource;

    @Column(name="cust_industry",columnDefinition = "varchar(32) DEFAULT NULL COMMENT '客户所属行业'")
    private String custIndustry;

    @Column(name = "cust_level",columnDefinition = "varchar(32) DEFAULT NULL COMMENT '客户级别'")
    private String custLevel;

    @Column(name = "cust_address",columnDefinition = "varchar(32) DEFAULT NULL COMMENT '客户联系地址'")
    private String custAddress;

    @Column(name="cust_phone",columnDefinition = "varchar(32) DEFAULT NULL COMMENT '客户联系电话'")
    private String custPhone;
    //配置客户和联系人之间的关系(一对多关系)
    /**
     * 使用注解的方式配置多表关系
     * 1声明关系
     * @OneToMany:配置一对多关系
     *      targetEntity:对方对象的字节码对象
     * 2外键
     * @JoinColumn:配置外键
     *      name:外键字段名称
     *      referencedColumnName:参照的主表主键字段名称
     *
     *      在客户(一对多的一方)添加了外键配置,所以对于客户而言,具备了维护外键的作用
     */
//    @OneToMany(targetEntity = LinkMan.class)
//    @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
    /**
     * 放弃一对多一的一方维护外键(因为会发送update语句维护外键,导致外键为空)
     * mappedBy:对方配置关系的属性名称
     * cascade:配置级联(配置到操作主体的多表的映射关系的注解上)
     *              CascadeType.All      所有
     *              CascadeType.MERGE    更新
     *              CascadeType.REMOVE   删除
     *              CascadeType.PERSIST  保存
     *
     * fetch: 配置加载时机
     *          FetchType.EAGER:立即加载
     *          FetchType.LAZY: 延迟加载
     */
    @OneToMany(mappedBy = "customer",cascade = CascadeType.ALL)
    private Set<LinkMan> linkMans = new HashSet<LinkMan>();

    public Long getCustId() {
        return custId;
    }

    public void setCustId(Long custId) {
        this.custId = custId;
    }

    public String getCustName() {
        return custName;
    }

    public void setCustName(String custName) {
        this.custName = custName;
    }

    public String getCustSource() {
        return custSource;
    }

    public void setCustSource(String custSource) {
        this.custSource = custSource;
    }

    public String getCustIndustry() {
        return custIndustry;
    }

    public void setCustIndustry(String custIndustry) {
        this.custIndustry = custIndustry;
    }

    public String getCustLevel() {
        return custLevel;
    }

    public void setCustLevel(String custLevel) {
        this.custLevel = custLevel;
    }

    public String getCustAddress() {
        return custAddress;
    }

    public void setCustAddress(String custAddress) {
        this.custAddress = custAddress;
    }

    public String getCustPhone() {
        return custPhone;
    }

    public void setCustPhone(String custPhone) {
        this.custPhone = custPhone;
    }

    public Set<LinkMan> getLinkMans() {
        return linkMans;
    }

    public void setLinkMans(Set<LinkMan> linkMans) {
        this.linkMans = linkMans;
    }

    @Override
    public String toString() {
        return "Customer{" +
                "custId=" + custId +
                ", custName='" + custName + '\'' +
                ", custSource='" + custSource + '\'' +
                ", custIndustry='" + custIndustry + '\'' +
                ", custLevel='" + custLevel + '\'' +
                ", custAddress='" + custAddress + '\'' +
                ", custPhone='" + custPhone + '\'' +
                ", linkMans=" + linkMans +
                '}';
    }
}

 LinkMan 

package com.xxx.proj.entity;

import javax.persistence.*;
import java.io.Serializable;

/**
 * TODO
 *
 * @author wuqirui@yihuacomputer.com
 * @since 2021/12/29
 */
@Entity
@Table(name = "cst_linkman")
public class LinkMan implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "lkm_id",columnDefinition = "bigint(32) NOT NULL COMMENT '联系人编号(主键)'")
    private Long lkmId;

    @Column(name = "lkm_name",columnDefinition = "varchar(16) DEFAULT NULL COMMENT '联系人姓名'")
    private String lkmName;

    @Column(name = "lkm_gender",columnDefinition = "char(1) DEFAULT NULL COMMENT '联系人性别'")
    private String lkmGender;

    @Column(name = "lkm_phone",columnDefinition = "varchar(16) DEFAULT NULL COMMENT '联系人办公电话'")
    private String lkmPhone;

    @Column(name = "lkm_mobile",columnDefinition = "varchar(16) DEFAULT NULL COMMENT '联系人手机'")
    private String lkmMobile;

    @Column(name = "lkm_email",columnDefinition = "varchar(64) DEFAULT NULL COMMENT '联系人邮箱'")
    private String lkmEmail;

    @Column(name = "lkm_position",columnDefinition = "varchar(16) DEFAULT NULL COMMENT '联系人职位'")
    private String lkmPosition;

    @Column(name = "lkm_memo",columnDefinition = "varchar(512) DEFAULT NULL COMMENT '联系人备注'")
    private String lkmMemo;

    //配置联系人和客户之间的关系(多对一关系)
    /**
     * 使用注解的方式配置多表关系
     * 1声明关系
     * @ManyToOne:配置多对一关系
     *      targetEntity:对方对象的字节码对象
     * 2外键
     * @JoinColumn:配置外键
     *      name:外键字段名称
     *      referencedColumnName:参照的主表主键字段名称
     *
     *      在联系人(一对多的多方)添加了外键配置,所以对于联系人而言,具备了维护外键的作用
     */
    @ManyToOne(targetEntity = Customer.class)
    @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
    private Customer customer;

    public Long getLkmId() {
        return lkmId;
    }

    public void setLkmId(Long lkmId) {
        this.lkmId = lkmId;
    }

    public String getLkmName() {
        return lkmName;
    }

    public void setLkmName(String lkmName) {
        this.lkmName = lkmName;
    }

    public String getLkmGender() {
        return lkmGender;
    }

    public void setLkmGender(String lkmGender) {
        this.lkmGender = lkmGender;
    }

    public String getLkmPhone() {
        return lkmPhone;
    }

    public void setLkmPhone(String lkmPhone) {
        this.lkmPhone = lkmPhone;
    }

    public String getLkmMobile() {
        return lkmMobile;
    }

    public void setLkmMobile(String lkmMobile) {
        this.lkmMobile = lkmMobile;
    }

    public String getLkmEmail() {
        return lkmEmail;
    }

    public void setLkmEmail(String lkmEmail) {
        this.lkmEmail = lkmEmail;
    }

    public String getLkmPosition() {
        return lkmPosition;
    }

    public void setLkmPosition(String lkmPosition) {
        this.lkmPosition = lkmPosition;
    }

    public String getLkmMemo() {
        return lkmMemo;
    }

    public void setLkmMemo(String lkmMemo) {
        this.lkmMemo = lkmMemo;
    }

    public Customer getCustomer() {
        return customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }

    @Override
    public String toString() {
        return "LinkMan{" +
                "lkmId=" + lkmId +
                ", lkmName='" + lkmName + '\'' +
                ", lkmGender='" + lkmGender + '\'' +
                ", lkmPhone='" + lkmPhone + '\'' +
                ", lkmMobile='" + lkmMobile + '\'' +
                ", lkmEmail='" + lkmEmail + '\'' +
                ", lkmPosition='" + lkmPosition + '\'' +
                ", lkmMemo='" + lkmMemo + '\'' +
                '}';
    }
}

4. Dao 

package com.xxx.proj.dao;
import com.xxx.proj.entity.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/**
 * TODO
 *
 * @author wuqirui@yihuacomputer.com
 * @since 2021/12/11
 */
public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {

}

package com.xxx.proj.dao;
import com.xxx.proj.entity.LinkMan;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

/**
 * TODO
 *
 * @author wuqirui@yihuacomputer.com
 * @since 2021/12/29
 */
public interface LinkManDao extends JpaRepository<LinkMan,Long>, JpaSpecificationExecutor<LinkMan> {
}

 5. Test

一对多 OneToManyTest

package com.xxx.proj.test;
import com.xxx.proj.dao.CustomerDao;
import com.xxx.proj.dao.LinkManDao;
import com.xxx.proj.entity.Customer;
import com.xxx.proj.entity.LinkMan;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

/**
 * TODO
 *
 * @author wuqirui@yihuacomputer.com
 * @since 2021/12/29
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class OneToManyTest {
    @Autowired
    private CustomerDao customerDao;
    @Autowired
    private LinkManDao linkManDao;

    @Test
    @Transactional //添加事务保持一致性
    @Rollback(false) //spring-data-jpa事务默认回滚,设置不自动回滚
    public void testAdd() {
        //创建一个客户,创建一个联系人
        Customer customer  = new Customer();
        customer.setCustName("百度");

        LinkMan linkMan1 = new LinkMan();
        linkMan1.setLkmName("小杨");
        LinkMan linkMan2 = new LinkMan();
        linkMan2.setLkmName("小黑");
//        报错原因:先insert再update,update才维护外键 insert的时候外键为空。(解决方法放弃一的一方维护关联关系)
        customer.getLinkMans().add(linkMan1);//配置了一的一方到多的一方的关联关系(放弃)
        customer.getLinkMans().add(linkMan2);//配置了一的一方到多的一方的关联关系(放弃)
//        不报错原因:insert的时候就维护外键关系,外键不会为null
        linkMan1.setCustomer(customer);//配置了多的一方到一的一方的关联关系
        linkMan2.setCustomer(customer);//配置了多的一方到一的一方的关联关系

        customerDao.save(customer);
        linkManDao.save(linkMan1);
        linkManDao.save(linkMan2);

    }
    @Test
    public void testSelect() {
        //取消一方toString或者取消一方toString中的对方,因为两边都get对方形成死循环
        Customer customer = customerDao.findOne(1L);
        System.out.println(customer);
    }

    /**
     * 级联添加
     * 保存客户的同时,保存客户所有的联系人
     * 需要在操作主体(客户)的实体类上,配置casacde属性
     */
    @Test
    @Transactional //添加事务保持一致性
    @Rollback(false) //spring-data-jpa事务默认回滚,设置不自动回滚
    public void testCascadeAdd() {
        Customer customer  = new Customer();
        customer.setCustName("百度2");

        LinkMan linkMan = new LinkMan();
        linkMan.setLkmName("小杨2");

        customer.getLinkMans().add(linkMan);
        linkMan.setCustomer(customer);
        //只保存客户,客户的所有联系人也保存
        customerDao.save(customer);
    }

    /**
     * 级联删除(一定一定一定谨慎使用,
     * 删除客户的同时,删除客户所有的联系人
     * 需要在操作主体(客户)的实体类上,配置casacde属性
     */
    @Test
    @Transactional //添加事务保持一致性
    @Rollback(false) //spring-data-jpa事务默认回滚,设置不自动回滚
    public void testDelete() {
        //删除从表数据,可以随时任意删除
        //主表不能随意删除,因为一的一方主键可能被外键引用所以不能随意删除
        //1.在默认情况下,它会把外键字段设置为null,然后删除主表数据,如果在数据库的表结构上,外键字段有非空约束就会报错
        //2.如果主表放弃了维护外键关联关系的权力,则不能删除(主键正在被占用,不能删除)它根本不会去更新从表的外键字段
        //3.如果还想删除,使用级联删除引用
        //4,如果没有从表数据引用,主表也能随便删除
        Customer customer = customerDao.findOne(1L);
        customerDao.delete(customer);

    }
}

 对象导航查询 ObjectQueryTest

package com.xxx.proj.test;
import com.xxx.proj.dao.CustomerDao;
import com.xxx.proj.dao.LinkManDao;
import com.xxx.proj.entity.Customer;
import com.xxx.proj.entity.LinkMan;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

import java.util.Set;

/**
 * TODO
 *
 * @author wuqirui@yihuacomputer.com
 * @since 2022/1/4
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class ObjectQueryTest {
    @Autowired
    CustomerDao customerDao;
    @Autowired
    LinkManDao linkManDao;

    /**
     * 测试对象导航查询(查询一个对象的时候,通过此对象查询所以的关联对象)
     * 必须使用@Transactional注解否则会报错:could not initialize proxy - no Session
     */
    @Test
    @Transactional
    public void testQuery1() {
        //getOne延迟加载
        Customer customer = customerDao.getOne(1L);
        Set<LinkMan> linkMans = customer.getLinkMans();
        for (LinkMan linkMan : linkMans) {
            System.out.println(linkMan);
        }
    }

    /**
     * 从一的对象导航查询到多的对象默认延迟加载,因为数据量太大
     * 对象导航查询:从客户对象查询他的所属联系人
     * 默认使用的是延迟加载查询,只有使用的时候才去查询
     * 可以通过修改查询主体(Customer)的关联关系注解@OneToMany中的fetch将延迟加载更改为立即加载
     *
     */
    @Test
    @Transactional
    public void testQuery2() {
        Customer customer = customerDao.findOne(1L);
        Set<LinkMan> linkMans = customer.getLinkMans();
        for (LinkMan linkMan : linkMans) {
            System.out.println(linkMan);
        }
    }

    /**
     * 从多的对象导航查询到一的对象默认立即加载,因为数据量少
     * 对象导航查询:从联系人对象导航查询他的所属客户
     * 默认使用立即加载
     * 可以通过修改查询主体(LinkMan)的关联关系注解@OneToMany中的fetch将立即加载更改为延迟加载
     */
    @Test
    @Transactional
    public void testQuery3() {
        LinkMan linkMan = linkManDao.findOne(2L);
        Customer customer = linkMan.getCustomer();
        System.out.println(customer);
    }

}


Spring Data Jpa多表:多对多

1. pom文件(跟一对多一致)

2. applicationContext.xml (跟一对多一致)

3. Entity 

  User

package com.xxx.proj.entity;

import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

/**
 * TODO
 *
 * @author wuqirui@yihuacomputer.com
 * @since 2022/1/4
 */
@Entity
@Table(name = "sys_user")
public class User implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    private long userId;
    @Column(name = "user_name")
    private String userName;
    @Column(name = "user_age")
    private Integer userAge;
    /**
     * 多对多关系映射
     * 1.声明表关系的配置
     *      @ManyToMany(targetEntity = Role.class)
     *          targetEntity:代表对方的实体类字节码对象
     * 2.配置中间表(包含两个外键)
     *      @JoinTable
     *          name:中间表表面
     *          joinColumns:配置当前对象在中间表中的外键
     *              @JoinColumn的数组
     *                  name:外键
     *                  referencedColumnName:参照的主表的主键
     *          inverseJoinColumns: 配置对方对象在中间表的外键
     */
    @ManyToMany(targetEntity = Role.class,cascade = CascadeType.ALL)
    @JoinTable(name = "sys_user_role", //name中间表表名
                //joinColumns当前对象在中间表中的外键
                joinColumns = {@JoinColumn(name = "sys_user_id",referencedColumnName = "user_id")},
                //inverseJoinColumns对方对象在中间表的外键
                inverseJoinColumns = {@JoinColumn(name = "sys_role_id",referencedColumnName = "role_id")}
    )
    private Set<Role> roles = new HashSet<Role>();

    public long getUserId() {
        return userId;
    }

    public void setUserId(long userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public Integer getUserAge() {
        return userAge;
    }

    public void setUserAge(Integer userAge) {
        this.userAge = userAge;
    }

    public Set<Role> getRoles() {
        return roles;
    }

    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                ", userAge=" + userAge +
                ", roles=" + roles +
                '}';
    }
}

 Role

package com.xxx.proj.entity;

import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

/**
 * TODO
 *
 * @author wuqirui@yihuacomputer.com
 * @since 2022/1/4
 */
@Entity
@Table(name = "sys_role")
public class Role implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "role_id")
    private Long roleId;
    @Column(name = "role_name")
    private String roleName;
    /**
     * 多对多关系映射
     * 1.声明表关系的配置
     *      @ManyToMany(targetEntity = Role.class)
     *          targetEntity:代表对方的实体类字节码对象
     * 2.配置中间表(包含两个外键)
     *      @JoinTable
     *          name:中间表表面
     *          joinColumns:配置当前对象在中间表中的外键
     *              @JoinColumn的数组
     *                  name:外键
     *                  referencedColumnName:参照的主表的主键
     *          inverseJoinColumns: 配置对方对象在中间表的外键
     */
    @ManyToMany(mappedBy = "roles")
    /**
     *  放弃中间表维护权
     */
//    @JoinTable(name = "sys_user_role", //name中间表表名
//                //joinColumns当前对象在中间表中的外键
//                joinColumns = {@JoinColumn(name = "sys_role_id",referencedColumnName = "role_id")},
//                //inverseJoinColumns对方对象在中间表的外键
//                inverseJoinColumns = {@JoinColumn(name = "sys_user_id",referencedColumnName = "user_id")}
//    )
    private Set<User> users = new HashSet<User>();

    public Long getRoleId() {
        return roleId;
    }

    public void setRoleId(Long roleId) {
        this.roleId = roleId;
    }

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public Set<User> getUsers() {
        return users;
    }

    public void setUsers(Set<User> users) {
        this.users = users;
    }

    @Override
    public String toString() {
        return "Role{" +
                "roleId=" + roleId +
                ", roleName='" + roleName + '\'' +
                '}';
    }
}

4. Dao 

package com.xxx.proj.dao;

import com.xxx.proj.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

/**
 * TODO
 *
 * @author wuqirui@yihuacomputer.com
 * @since 2022/1/4
 */

public interface UserDao extends JpaRepository<User,Long>, JpaSpecificationExecutor<User> {

}
package com.xxx.proj.dao;

import com.xxx.proj.entity.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

/**
 * TODO
 *
 * @author wuqirui@yihuacomputer.com
 * @since 2022/1/4
 */

public interface RoleDao extends JpaRepository<Role,Long>, JpaSpecificationExecutor<Role> {

}

5. Test 

  多对多 ManyToManyTest 

package com.xxx.proj.test;

import com.xxx.proj.dao.RoleDao;
import com.xxx.proj.dao.UserDao;
import com.xxx.proj.entity.Role;
import com.xxx.proj.entity.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

/**
 * TODO
 *
 * @author wuqirui@yihuacomputer.com
 * @since 2022/1/4
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class ManyToManyTest {
    @Autowired
    UserDao userDao;
    @Autowired
    RoleDao roleDao;

    /**
     * 保存用户,保存角色
     * 多对多放弃维护权(不放弃会报主键重复冲突,所以放弃一方维护权),被动的一方放弃维护权
     */
    @Test
    @Transactional
    @Rollback(false)
    public void testAdd() {
        User user = new User();
        user.setUserName("小张");

        Role role1 = new Role();
        role1.setRoleName("java");
        Role role2 = new Role();
        role2.setRoleName("打工人");
        Role role3 = new Role();
        role3.setRoleName("互联网");
        //配置用户到角色关系,可以对中间表中的数据进行维护
        user.getRoles().add(role1);
        user.getRoles().add(role2);
        user.getRoles().add(role3);
        //配置角色到用户关系,可以对中间表中的数据进行维护(必须放弃角色的中间表维护全,要不然会报主键重复错误
        role1.getUsers().add(user);
        role2.getUsers().add(user);
        role3.getUsers().add(user);

        userDao.save(user);
        roleDao.save(role1);
        roleDao.save(role2);
        roleDao.save(role3);
    }

    /**
     * 级联添加(保存用户的同时,保存用户关联的角色信息)
     * 操作主体的@ManyToMany注解上,添加cascade = CascadeType.ALL
     */
    @Test
    @Transactional
    @Rollback(false)
    public void testCascadeAdd() {
        User user = new User();
        user.setUserName("小李");

        Role role1 = new Role();
        role1.setRoleName("java");
        Role role2 = new Role();
        role2.setRoleName("打工人");
        Role role3 = new Role();
        role3.setRoleName("互联网");

        //配置用户到角色关系,可以对中间表中的数据进行维护
        user.getRoles().add(role1);
        user.getRoles().add(role2);
        user.getRoles().add(role3);
        //配置角色到用户关系,可以对中间表中的数据进行维护(必须放弃角色的中间表维护全,要不然会报主键重复错误
        role1.getUsers().add(user);
        role2.getUsers().add(user);
        role3.getUsers().add(user);

        userDao.save(user);
    }

    /**
     * 级联删除(一定一定一定谨慎使用,
     */
    @Test
    @Transactional
    @Rollback(false)
    public void testCascadeDeleteTest() {
        User user = userDao.findOne(1l);
        userDao.delete(user);
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值