多表操作
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方法
}
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>();
項目結構: