天天看點

11、多表操作--一對多多表操作

多表操作

1、表關系回顧

​  表關系

​   一對一

​   一對多:

​     一的一方:主表

多的一方:從表

​     外鍵:需要再從表上建立一列作為外鍵,他的取值來源于主表的主鍵

​   多對多:

​     中間表:中間表中最少應該由兩個字段組成,這兩個字段作為外鍵指向兩張表的主鍵,又組合成了聯合主鍵

​ 講師對學員:一對多關系

​ 實體類中的關系:

​   包含關系:可以通過實體類中的包含關系描述表關系

​   繼承關系:

​ 分析步驟

​   1.明确表關系

​   2.确定表關系(描述 外鍵|中間表)

​   3.編寫實體類,在實體類中描述表關系(包含關系)

​   4.配置映射關系

多表操作:

​ i.一對多操作

​   案例:客戶和聯系人的案例(一對多關系)

​     客戶:一家公司

​     聯系人:這家公司的員工

​   分析步驟

​    1.明确表關系

​      一對多關系

​    2.确定表關系(描述 外鍵|中間表)

​       主表:客戶表

​       從表:聯系人表

​          * 在從表上添加外鍵

​    3.編寫實體類,在實體類中描述表關系(包含關系)

​       客戶:在客戶的實體類中包含一個 聯系人的集合

​       聯系人:在聯系人的實體類中包含一個客戶的對象

​    4.配置映射關系

​      * 使用jpa注解配置一對多映射關系

2、案例

## 2.1、配置環境
           

​ 1、建立資料庫

/*建立客戶表*/
CREATE TABLE cst_customer (
  cust_id bigint(32) NOT NULL AUTO_INCREMENT COMMENT '客戶編号(主鍵)',
  cust_name varchar(32) NOT NULL COMMENT '客戶名稱(公司名稱)',
  cust_source varchar(32) DEFAULT NULL COMMENT '客戶資訊來源',
  cust_industry varchar(32) DEFAULT NULL COMMENT '客戶所屬行業',
  cust_level varchar(32) DEFAULT NULL COMMENT '客戶級别',
  cust_address varchar(128) DEFAULT NULL COMMENT '客戶聯系位址',
  cust_phone varchar(64) DEFAULT NULL COMMENT '客戶聯系電話',
  PRIMARY KEY (`cust_id`)
) ENGINE=InnoDB AUTO_INCREMENT=94 DEFAULT CHARSET=utf8;

/*建立聯系人表*/
CREATE TABLE cst_linkman (
  lkm_id bigint(32) NOT NULL AUTO_INCREMENT COMMENT '聯系人編号(主鍵)',
  lkm_name varchar(16) DEFAULT NULL COMMENT '聯系人姓名',
  lkm_gender char(1) DEFAULT NULL COMMENT '聯系人性别',
  lkm_phone varchar(16) DEFAULT NULL COMMENT '聯系人辦公電話',
  lkm_mobile varchar(16) DEFAULT NULL COMMENT '聯系人手機',
  lkm_email varchar(64) DEFAULT NULL COMMENT '聯系人郵箱',
  lkm_position varchar(16) DEFAULT NULL COMMENT '聯系人職位',
  lkm_memo varchar(512) DEFAULT NULL COMMENT '聯系人備注',
  lkm_cust_id bigint(32) NOT NULL COMMENT '客戶id(外鍵)',
  PRIMARY KEY (`lkm_id`),
  KEY `FK_cst_linkman_lkm_cust_id` (`lkm_cust_id`),
  CONSTRAINT `FK_cst_linkman_lkm_cust_id` FOREIGN KEY (`lkm_cust_id`) REFERENCES `cst_customer` (`cust_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
           

​ 2、配置springdatajpa的配置環境 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.sddm.entity" />
        <!-- jpa的實作廠家 -->
        <property name="persistenceProvider">
            <bean class="org.hibernate.jpa.HibernatePersistenceProvider" />
        </property>

        <!--JPA的供應商擴充卡-->
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <!--是否自動建立資料庫表-->
                <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實作方式(hibernete)的配置資訊
            hibernate.hbm2ddl.auto:自動建立資料庫表
                create:每次都會重新建立資料庫表
                update:有表不會重新建立,沒有表會重新建立表
        -->
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.hbm2ddl.auto">create</prop>
            </props>
        </property>
    </bean>

    <!-- dataSource 配置資料庫連接配接池-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver" />
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/jpa" />
        <property name="user" value="root" />
        <property name="password" value="root" />
    </bean>

    <!-- 整合spring data jpa-->
    <jpa:repositories base-package="com.sddm.dao"
                      transaction-manager-ref="transactionManager"
                      entity-manager-factory-ref="entityManagerFactory"></jpa:repositories>

    <!-- 事務管理器-->
    <!-- JPA事務管理器  -->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>

    <!-- txAdvice-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="save*" propagation="REQUIRED"/>
            <tx:method name="insert*" propagation="REQUIRED"/>
            <tx:method name="update*" propagation="REQUIRED"/>
            <tx:method name="delete*" propagation="REQUIRED"/>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <!-- aop-->
    <aop:config>
        <aop:pointcut id="pointcut" expression="execution(* com.sddm.service.*.*(..))" />
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut" />
    </aop:config>

    <context:component-scan base-package="com.sddm"></context:component-scan>

    <!--組裝其它 配置檔案-->

</beans>
           

​ 3、編寫實體類

​ 客戶:customer實體

@Entity
@Table(name = "cst_customer")
public class Customer {
    @Id//聲明目前私有屬性為主鍵
    @GeneratedValue(strategy= GenerationType.IDENTITY) //配置主鍵的生成政策
    @Column(name="cust_id") //指定和表中cust_id字段的映射關系
    private Long custId;

    @Column(name="cust_name") //指定和表中cust_name字段的映射關系
    private String custName;

    @Column(name="cust_source")
    private String custSource;

    @Column(name="cust_industry")
    private String custIndustry;

    @Column(name="cust_level")
    private String custLevel;

    @Column(name="cust_address")
    private String custAddress;

    @Column(name="cust_phone")
    private String custPhone;
    
    //配置客戶和聯系人之間的關系(一對多關系)
    /*
    * 使用注解的形式配置多表關系
    *      1.聲明關系
    *        @OneToMany:配置一對多關系
    *           targetEntity:對方對象的位元組碼對象
    *      2.配置外鍵(中間表)
    *        @JoinColumn:配置外鍵
    *           name:外鍵字段名稱
    *           referencedColumnName:參照的主表的主鍵字段名稱
    *
    *  * 在客戶實體上(一的一方)添加外鍵的配置,是以對于客戶而言,也具備了維護外鍵的作用
    * */
    @OneToMany(targetEntity = LinkMan.class)
    @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
    private Set<LinkMan> linkMans = new HashSet<LinkMan>();

	//省略get/set方法
}
           

​ 聯系人:linkMan實體

@Entity
@Table(name = "cst_linkman")
public class LinkMan {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "lkm_id")
    private Long lkmId; //聯系人編号(主鍵)
    @Column(name = "lkm_name")
    private String lkmName; //聯系人姓名
    @Column(name = "lkm_gender")
    private String lkmGender; //聯系人性别
    @Column(name = "lkm_phone")
    private String lkmPhone; //聯系人辦公電話
    @Column(name = "lkm_mobile")
    private String lkmMobile; //聯系人手機
    @Column(name = "lkm_email")
    private String lkmEmail; //聯系人郵箱
    @Column(name = "lkm_position")
    private String lkmPosition; //聯系人職位
    @Column(name = "lkm_memo")
    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;
 
 	//省略get、set方法
}
           

​ 4、編寫dao接口

​ 客戶:CustomerDao

public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {
}
           

​ 聯系人:LinkManDao

public interface LinkManDao extends JpaRepository<LinkMan,Long>, JpaSpecificationExecutor<LinkMan> {
}
           

2.2、測試案例

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class OnetoManyTest {

    @Autowired
    private CustomerDao customerDao;

    @Autowired
    private LinkManDao linkManDao;
}
           

2.2.1、一對多的儲存案例

/*
* 儲存一個客戶,儲存一個聯系人
* */
@Test
@Transactional //配置事務
@Rollback(false)//不自動復原
public void testAdd(){
    //建立一個客戶,建立一個聯系人
    Customer customer  = new Customer();
    customer.setCustName("百度");

    LinkMan linkMan = new LinkMan();
    linkMan.setLkmName("小李");

    /*
     * 配置了客戶到聯系人的關系
     *       從客戶的角度上:發送兩條insert語句,發送一條更新語句更新資料庫(更新外鍵)
     * 由于我們配置了客戶到聯系人的關系:客戶可以對外鍵進行維護
     * */
    customer.getLinkMans().add(linkMan);

    customerDao.save(customer);
    linkManDao.save(linkMan);
}
           
@Test
@Transactional //配置事務
@Rollback(false)//不自動復原
public void testAdd1(){
    //建立一個客戶,建立一個聯系人
    Customer customer  = new Customer();
    customer.setCustName("百度");

    LinkMan linkMan = new LinkMan();
    linkMan.setLkmName("小李");

    /*
    * 配置聯系人到客戶的關系(多對一)
    *   隻發送了兩條insert語句
    * 由于配置了聯系人到客戶的映射關系(多對一)
    * */
    linkMan.setCustomer(customer);

    customerDao.save(customer);
    linkManDao.save(linkMan);
}
           
/*
* 會有一條多餘的update語句
*       * 由于一的一方可以維護外鍵:會發送update語句
*       * 解決此問題:隻需要在一的一方放棄維護權即可
* */
@Test
@Transactional //配置事務
@Rollback(false)//不自動復原
public void testAdd2(){
    //建立一個客戶,建立一個聯系人
    Customer customer  = new Customer();
    customer.setCustName("百度");

    LinkMan linkMan = new LinkMan();
    linkMan.setLkmName("小李");

    customer.getLinkMans().add(linkMan);
    linkMan.setCustomer(customer);

    customerDao.save(customer);
    linkManDao.save(linkMan);
}
           

通過儲存的案例,我們可以發現在設定了雙向關系之後,會發送兩條insert語句,一條多餘的update語句,那我們的解決是思路很簡單,就是一的一方放棄維護權

@Entity
@Table(name = "cst_customer")
public class Customer {
    @Id//聲明目前私有屬性為主鍵
    @GeneratedValue(strategy= GenerationType.IDENTITY) //配置主鍵的生成政策
    @Column(name="cust_id") //指定和表中cust_id字段的映射關系
    private Long custId;

    @Column(name="cust_name") //指定和表中cust_name字段的映射關系
    private String custName;

    @Column(name="cust_source")
    private String custSource;

    @Column(name="cust_industry")
    private String custIndustry;

    @Column(name="cust_level")
    private String custLevel;

    @Column(name="cust_address")
    private String custAddress;

    @Column(name="cust_phone")
    private String custPhone;

	//@OneToMany(targetEntity = LinkMan.class)
	//@JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
    /*
    * 放棄外鍵維護權
    *   mappedBy:對方配置關系的屬性名稱
    * */
    @OneToMany(mappedBy = "customer")
    private Set<LinkMan> linkMans = new HashSet<LinkMan>();
   
   	//省略get,set方法
}
           
11、多表操作--一對多多表操作

2.2.3 映射的注解說明

@OneToMany:

 作用:建立一對多的關系映射

 

 屬性:

​   targetEntityClass:指定多的多方的類的位元組碼

​   mappedBy:指定從表實體類中引用主表對象的名稱。

​   cascade:指定要使用的級聯操作

​   fetch:指定是否采用延遲加載

​   orphanRemoval:是否使用孤兒删除

@ManyToOne

 作用:建立多對一的關系

 

 屬性:

​   targetEntityClass:指定一的一方實體類位元組碼

​   cascade:指定要使用的級聯操作

​   fetch:指定是否采用延遲加載

​   optional:關聯是否可選。如果設定為false,則必須始終存在非空關系。

@JoinColumn

 作用:用于定義主鍵字段和外鍵字段的對應關系。

 

 屬性:

​   name:指定外鍵字段的名稱

​   referencedColumnName:指定引用主表的主鍵字段名稱

​   unique:是否唯一。預設值不唯一

​   nullable:是否允許為空。預設值允許。

​   insertable:是否允許插入。預設值允許。

​   updatable:是否允許更新。預設值允許。

​   columnDefinition:列的定義資訊。

2.2.4、删除

删除從表資料:可以随時任意删除。

删除主表資料:

有從表資料

1、在預設情況下,它會把外鍵字段置為null,然後删除主表資料。如果在資料庫的表 結構上,外鍵字段有非空限制,預設情況就會報錯了。

2、如果配置了放棄維護關聯關系的權利,則不能删除(與外鍵字段是否允許為null, 沒有關系)因為在删除時,它根本不會去更新從表的外鍵字段了。

3、如果還想删除,使用級聯删除引用

在實際開發中,級聯删除請慎用!(在一對多的情況下)

2.2.4.1 級聯

級聯:

​   操作一個對象的同僚操作他的關聯對象

​   級聯操作:

​     1、需要區分操作主題

​     2、需要在操作主體的實體類上,添加級聯屬性(需要添加到多表映射關系的注解上)

​     3、cascade(配置級聯)

​ 級聯添加,

​    案例:當我們儲存一個客戶的同時儲存聯系人

​ 級聯删除

​    案例:當我們删除一個客戶的同時删除此客戶的所有聯系人

/*
 * 級聯添加:一個客戶的同時,儲存客戶的所有聯系人
 *        需要在操作主體的實體類上,配置casacde屬性
 * */
@Test
@Transactional //配置事務
@Rollback(false)//不自動復原
public void testCascadeAdd(){
    //建立一個客戶,建立一個聯系人
    Customer customer  = new Customer();
    customer.setCustName("百度1");

    LinkMan linkMan1 = new LinkMan();
    linkMan1.setLkmName("小李1");

    LinkMan linkMan2 = new LinkMan();
    linkMan2.setLkmName("小李2");

    customer.getLinkMans().add(linkMan1);
    customer.getLinkMans().add(linkMan2);
    linkMan1.setCustomer(customer);
    linkMan2.setCustomer(customer);

    customerDao.save(customer);
}

/*
 * 級聯删除
 *      删除1号客戶的同時,删除1号客戶的所有聯系人
 *
 * */
@Test
@Transactional //配置事務
@Rollback(false)//不自動復原
public void testCascadeRemove(){
    //1、查詢1号客戶
    Customer customer = customerDao.findOne(1l);
    //2、删除1号客戶
    customerDao.delete(customer);
}
           

2.2.4.2 級聯操作

級聯操作:指操作一個對象同時操作它的關聯對象

使用方法:隻需要在操作主體的注解上配置cascade

/*
 * 放棄外鍵維護權
 *   mappedBy:對方配置關系的屬性名稱
 *
 * cascade:配置級聯(可以配置到設定多表的映射關系的注解上)
 *       CascadeType.all     : 所有
 *                   MERGE   :更新
 *                   PERSIST :儲存
 *                   REMOVE  :删除
 * */
 @OneToMany(mappedBy = "customer",cascade = CascadeType.ALL)
 private Set<LinkMan> linkMans = new HashSet<LinkMan>();
           

項目結構:

11、多表操作--一對多多表操作