ORM规范: JPA

JPA(Java Persistence API)未提供ORM实现,只制订了一些规范,提供了一些编程的API接口,具体实现需要由服务厂商实现。

使用JPA规范进行数据库操作,底层需要hibernate作为实现类完成数据持久化工作:
在这里插入图片描述
JPA: JPA配置

一、基本使用

在这里插入图片描述

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=1 DEFAULT CHARSET=utf8;
2、编写实体类

@Entity: 指定当前类是实体类
@Table:指定实体类和表之间的对应关系

  • name:指定数据库表的名称

@Id:指定当前字段是主键
@GeneratedValue:指定主键的生成方式

  • strategy :指定主键生成策略

@Column:指定实体类属性和数据表字段之间的对应关系

  • name:指定数据表的字段名称。
  • unique:是否唯一
  • nullable:是否可以为空
  • inserttable:是否可以插入
  • updateable:是否可以更新
  • columnDefinition: 定义建表时创建此列的DDL
  • secondaryTable: 从表名。

在实体类上使用JPA注解的形式配置映射关系

@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")//指定和表中cust_source字段的映射关系
	private String custSource;
	
	@Column(name="cust_industry")//指定和表中cust_industry字段的映射关系
	private String custIndustry;
	
	@Column(name="cust_level")//指定和表中cust_level字段的映射关系
	private String custLevel;
	
	@Column(name="cust_address")//指定和表中cust_address字段的映射关系
	private String custAddress;
	
	@Column(name="cust_phone")//指定和表中cust_phone字段的映射关系
	private String custPhone;
.......
}
3、主键生成策略

JPA提供的四种标准用法为TABLE,SEQUENCE,IDENTITY,AUTO

IDENTITY:主键由数据库自动生成(主要是自动增长型)

@Id  
@GeneratedValue(strategy = GenerationType.IDENTITY) 
private Long custId;

SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列。

@Id  
@GeneratedValue(strategy = GenerationType.SEQUENCE,generator="payablemoney_seq")  
@SequenceGenerator(name="payablemoney_seq", sequenceName="seq_payment")  
private Long custId;

AUTO:主键由程序控制

@Id  
@GeneratedValue(strategy = GenerationType.AUTO)  
private Long custId;

TABLE:使用一个特定的数据库表格来保存主键

@Id  
@GeneratedValue(strategy = GenerationType.TABLE, generator="payablemoney_gen")  
@TableGenerator(name = "pk_gen",  
table="tb_generator",  
pkColumnName="gen_name",  
valueColumnName="gen_value",  
pkColumnValue="PAYABLEMOENY_PK",  
allocationSize=1  ) 
private Long custId;
二、Spring Data JPA
1.创建一个Dao层接口

实现JpaRepository和JpaSpecificationExecutor接口

public interface CustomerDao extends
 JpaRepository<Customer, Long>,
 JpaSpecificationExecutor<Customer> {
}
  • JpaRepository<实体类类型,主键类型>:用来完成基本CRUD操作
  • JpaSpecificationExecutor<实体类类型>:用于复杂查询(分页等查询操作)
2、完成基本CRUD操作
@Autowired
private CustomerDao customerDao;

public void testSave() {
  Customer c = new Customer();
  c.setCustName("hello");
  customerDao.save(c);
}
如果此对象中存在id属性,会先根据id查询,再进行更新操作;    
如果对象中不存在id属性,即为保存操作;
//根据id查询id为1的客户
Customer customer = customerDao.findOne(1l);
//修改客户名称
customer.setCustName("传智播客顺义校区");
//更新
customerDao.save(customer);
//根据id删除
customerDao.delete(1l);
3、使用JPQL的方式查询

对于某些业务,需要灵活的构造查询条件,这时就需要使用@Query注解,结合JPQL的语句方式完成查询

public interface CustomerDao extends JpaRepository<Customer, Long>,JpaSpecificationExecutor<Customer> {    
    @Query(value="from Customer")
    public List<Customer> findAllCustomer();
    
    //@Query 使用jpql的方式查询。?1代表参数的占位符
    @Query(value="from Customer where custName = ?1")
    public Customer findCustomer(String custName);
}

JPQL模糊查询,并判断参数是否为null

@Query(value="select c from Customer c where 1=1 and
c.custName is null or c.custName like %?1%")
public Customer findCustomer(String custName);

使用 @Query 来执行更新操作,用 @Modifying 来将该操作标识为修改查询

@Query(value="update Customer set custName = ?1 where custId = ?2")
@Modifying
public void updateCustomer(String custName,Long custId);
4、使用SQL语句查询

nativeQuery : 使用本地sql的方式查询

@Query(value="select * from cst_customer",nativeQuery=true)
public void findSql();

模糊查询,并判断参数是否为null:

@Query(value="select c.* from customer c where 
if(:cust_name !=null,c.custName like concat('%',:cust_name,'%'),1=1)
,nativeQuery=true)
public Customer findCustomer(@Param(value = "cust_name ")String custName);
@Query(value="select c.* from customer c where 
:cust_name is null or (c.custName like concat('%',:cust_name,'%'))
,nativeQuery=true)
public Customer findCustomer(@Param(value = "cust_name ")String custName);
5、方法命名规则查询

根据方法的名字,创建查询

//方法命名方式查询(根据客户名称查询客户)
public Customer findByCustName(String custName);
KeywordSampleJPQL
AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2
OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2
Is,EqualsfindByFirstnameIs, findByFirstnameEquals… 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
AfterfindByStartDateAfter… where x.startDate > ?1
BeforefindByStartDateBefore… where x.startDate < ?1
IsNullfindByAgeIsNull… where x.age is null
IsNotNull,NotNullfindByAge(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 ?1 (parameter bound with appended %)
EndingWithfindByFirstnameEndingWith… where x.firstname like ?1 (parameter bound with prepended %)
ContainingfindByFirstnameContaining… where x.firstname like ?1 (parameter bound wrapped in %)
OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname desc
NotfindByLastnameNot… where x.lastname <> ?1
InfindByAgeIn(Collection ages)… where x.age in ?1
NotInfindByAgeNotIn(Collection age)… where x.age not in ?1
TRUEfindByActiveTrue()… where x.active = true
FALSEfindByActiveFalse()… where x.active = false
IgnoreCasefindByFirstnameIgnoreCase… where UPPER(x.firstame) = UPPER(?1)
三、Specifications动态查询

在查询某个实体的时候,给定的条件不固定时,就需要动态构建相应的查询语句。在Spring Data JPA中可以通过JpaSpecificationExecutor接口查询。

JpaSpecificationExecutor中定义的方法:

public interface JpaSpecificationExecutor<T> {
   	//根据条件查询一个对象
 	T findOne(Specification<T> spec);	
   	//根据条件查询集合
 	List<T> findAll(Specification<T> spec);
   	//根据条件分页查询
 	Page<T> findAll(Specification<T> spec, `在这里插入代码片`Pageable pageable);
   	//排序查询查询
 	List<T> findAll(Specification<T> spec, Sort sort);
   	//统计查询
 	long count(Specification<T> spec);
}

可以简单的理解为,Specification构造的就是查询条件。
Specification接口中只定义了一个方法:

root :可以通过root获取实体中的属性
query :代表一个顶层查询对象,用来自定义查询
cb :用来构建查询,此对象里有很多条件方法

public Predicate toPredicate(Root<T> root,
 CriteriaQuery<?> query, CriteriaBuilder cb);
1、使用Specifications完成条件查询
@Autowired
private CustomerDao customerDao;	

public void testSpecifications() {
//使用匿名内部类的方式,创建一个Specification的实现类,并实现toPredicate方法
Specification <Customer> spec = new Specification<Customer>() {
  public Predicate toPredicate(Root<Customer>   root,     CriteriaQuery<?> query, CriteriaBuilder cb) {
    //cb:构建查询,添加查询方式   like:模糊匹配
    //root:从实体Customer对象中按照custName属性进行查询
    return cb.like(root.get("custName").as(String.class), "hello%");
  }
};
Customer customer = customerDao.findOne(spec);
System.out.println(customer);
}
2、基于Specifications完成分页查询
/**
* 构造分页参数
* 		Pageable : 接口
* 			PageRequest实现了Pageable接口,调用构造方法的形式构造
* 				第一个参数:页码(从0开始)
* 				第二个参数:每页查询条数
*/
Pageable pageable = new PageRequest(0, 5);
		
/**
* 分页查询,封装为Spring Data Jpa 内部的page bean
* 		此重载的findAll方法为分页方法需要两个参数
* 			第一个参数:查询条件Specification
* 			第二个参数:分页参数
*/
Page<Customer> page = customerDao.findAll(spec,pageable);
四、多表设计

表关系:一对一、一对多(多对一)、多对多

1、一对多

客户:指的是一家公司,我们记为A。
联系人:指的是A公司中的员工。

  • 习惯把一的一方称之为主表,把多的一方称之为从表
  • 外键:从表中有一列,取值参照主表的主键
    在这里插入图片描述
    在实体类中,由于客户是少的一方,它应该包含多个联系人,所以实体类要体现出客户中有多个联系人的信息

客户实体类(一方):

@Table(name="cst_customer")
public class Customer implements Serializable {
	
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(name="cust_id")
	private Long custId;
	
    //配置客户和联系人的一对多关系
  	@OneToMany(targetEntity=LinkMan.class)
    @JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")
	private Set<LinkMan> linkmans = new HashSet<LinkMan>(0);
}

联系人实体类(多方)

@Entity
@Table(name="cst_linkman")
public class LinkMan implements Serializable {
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(name="lkm_id")
	private Long lkmId;

	//多对一关系映射:多个联系人对应客户
	@ManyToOne(targetEntity=Customer.class)
	@JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")
	private Customer customer;//用它的主键,对应联系人表中的外键
}

@OneToMany: 建立一对多的关系映射

  • targetEntityClass:指定多的多方的类的字节码
  • mappedBy:指定从表实体类中引用主表对象的名称。
  • cascade:指定要使用的级联操作
  • fetch:指定是否采用延迟加载
  • orphanRemoval:是否使用孤儿删除

@ManyToOne:建立多对一的关系

  • targetEntityClass:指定一的一方实体类字节码
  • cascade:指定要使用的级联操作
  • fetch:指定是否采用延迟加载
  • optional:关联是否可选。如果设置为false,则必须始终存在非空关系。

@JoinColumn:定义主键字段和外键字段的对应关系
name:指定外键字段的名称

  • referencedColumnName:指定引用主表的主键字段名称
  • unique:是否唯一。默认值不唯一
  • nullable:是否允许为空。默认值允许。
  • insertable:是否允许插入。默认值允许。
  • updatable:是否允许更新。默认值允许。
  • columnDefinition:列的定义信息。

设置了双向关系之后,会发送两条insert语句,一条update语句,要想去掉多余的update语句,就需要为”一“的一方放弃外键维护权

@Test
	@Transactional  //开启事务
	@Rollback(false)//设置为不回滚
	public void testAdd() {
		Customer c = new Customer();
		c.setCustName("TBD云集中心");
		LinkMan l = new LinkMan();
		l.setLkmName("TBD联系人");
		c.getLinkMans().add(l);
		l.setCustomer(c);
		customerDao.save(c);
		linkManDao.save(l);
	}
}
    //配置客户和联系人的一对多关系
  	@OneToMany(mappedBy="customer")
	private Set<LinkMan> linkmans = new HashSet<LinkMan>(0);

删除:

  • 删除从表数据:可以随时任意删除;
  • 删除主表数据(有从表数据):
    (1)默认情况下,它会把外键字段置为null,然后删除主表数据。如果外键字段有非空约束,就会报错。
    (2)如果放弃了外键维护权,则不能删除。因为在删除时,它不会去更新从表的外键字段了。
    (3)如果还想删除,使用级联删除引用

在这里插入图片描述

2、级联操作

级联操作指操作一个对象同时操作它的关联对象。
cascade:配置级联操作

  • CascadeType.MERGE 级联更新
  • CascadeType.REFRESH 级联刷新
  • CascadeType.REMOVE 级联删除
  • CascadeType.ALL 包含所有
@OneToMany(mappedBy="customer",cascade=CascadeType.ALL)

在保存客户的时候,因为配置了级联操作,联系人也一起保存了。

    @Test
	@Transactional  //开启事务
	@Rollback(false)//设置为不回滚
	public void testAdd() {
		Customer c = new Customer();
		c.setCustName("TBD云集中心");
		LinkMan l = new LinkMan();
		l.setLkmName("TBD联系人");
		c.getLinkMans().add(l);
		l.setCustomer(c);
		customerDao.save(c);	}
}

注意:
在一对多的情况下,级联删除请慎用!

3、多对多

一个用户可以具有多个角色,所以在用户实体类中应该包含多个角色的信息

//多对多关系映射
public class SysUser implements Serializable {
    @Id	
    @GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(name="user_id")
	private Long userId;
	
	@ManyToMany
	@JoinTable(name="user_role_rel",
	joinColumns={@JoinColumn(name="user_id",referencedColumnName="user_id")},
	inverseJoinColumns={@JoinColumn(name="role_id",referencedColumnName="role_id")}
	)
	private Set<SysRole> roles = new HashSet<SysRole>(0);
}

一个角色可以赋予多个用户,所以在角色实体类中应该包含多个用户的信息

@Entity
@Table(name="sys_role")
public class SysRole implements Serializable {
	@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(name="role_id")
	private Long roleId;

	//多对多关系映射
	@ManyToMany
	@JoinTable(name="user_role_rel",//中间表的名称
	//中间表user_role_rel字段关联sys_role表的主键字段role_id
	joinColumns={@JoinColumn(name="role_id",referencedColumnName="role_id")},
	//中间表user_role_rel的字段关联sys_user表的主键user_id
	inverseJoinColumns={@JoinColumn(name="user_id",referencedColumnName="user_id")}
	)
	private Set<SysUser> users = new HashSet<SysUser>(0);
}

在多对多(保存)中,如果双向都设置关系,意味着双方都维护中间表,都会往中间表插入数据,中间表的两个字段又作为联合主键,所以报错,主键重复。解决问题,只需要在任意一方放弃对中间表的维护权即可,推荐在被动的一方放弃

    @Test
	@Transactional  //开启事务
	@Rollback(false)//设置为不回滚
	public void test1(){
		//创建对象
		SysUser u1 = new SysUser();
		u1.setUserName("用户1");
		SysRole r1 = new SysRole();
		r1.setRoleName("角色1");
		//建立关联关系
		u1.getRoles().add(r1);
		r1.getUsers().add(u1);
		//保存
		roleDao.save(r1);
		userDao.save(u1);
	}

任意一方,放弃对中间表的维护权:

	//放弃对中间表的维护权,解决保存中主键冲突的问题
	@ManyToMany(mappedBy="roles")
	private Set<SysUser> users = new HashSet<SysUser>(0);

@ManyToMany 用于映射多对多关系

  • cascade:配置级联操作。
  • fetch:配置是否采用延迟加载。
  • targetEntity:配置目标的实体类(映射多对多的时候不用写)

@JoinTable 针对中间表的配置

  • nam:配置中间表的名称
  • joinColumns:中间表的外键字段关联当前实体类所对应表的主键字段
  • inverseJoinColumn:中间表的外键字段关联对方表的主键字段

@JoinColumn 用于定义主键字段和外键字段的对应关系。

  • name:指定外键字段的名称
  • referencedColumnName:指定引用主表的主键字段名称
  • unique:是否唯一。默认值不唯一
  • nullable:是否允许为空。默认值允许。
  • insertable:是否允许插入。默认值允许。
  • updatable:是否允许更新。默认值允许。
  • columnDefinition:列的定义信息。

支持级联操作

5、延迟加载

延迟加载:通过配置延迟加载,在需要使用时,发起真正的查询。

fetch属性

  • FetchType.EAGER :立即加载
  • FetchType.LAZY :延迟加载
@OneToMany(mappedBy="customer",fetch=FetchType.EAGER)
private Set<LinkMan> linkMans = new HashSet<>(0);
6、使用Specification多表查询

Join代表链接查询(通过root对象获取),
Join的第一个参数为关联对象的属性名称,第二个参数为连接查询的方式(left,inner,right)

  • JoinType.LEFT : 左外连接
  • JoinType.INNER:内连接
  • JoinType.RIGHT:右外连接
/**
	 * Specification的多表查询
	 */
	@Test
	public void testFind() {
		Specification<LinkMan> spec = new Specification<LinkMan>() {
			public Predicate toPredicate(Root<LinkMan> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//Join代表链接查询,通过root对象获取
//创建的过程中,第一个参数为关联对象的属性名称,第二个参数为连接查询的方式(left,inner,right)
//JoinType.LEFT : 左外连接,JoinType.INNER:内连接,JoinType.RIGHT:右外连接
				Join<LinkMan, Customer> join = root.join("customer",JoinType.INNER);
				return cb.like(join.get("custName").as(String.class),"传智播客1");
			}
		};
		List<LinkMan> list = linkManDao.findAll(spec);
		for (LinkMan linkMan : list) {
			System.out.println(linkMan);
		}
	}
五、其他常用注解

1、@ForeignKey
为了提升数据库的查询速度,或是不想处理关联删除报错的问题。在进行数据表生成时,加入@ManyToOne,但却不想生成外键,可以使用如下方法解决:

@ManyToOne
@JoinColumn(foreignKey = 
@ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
private DanWei applyDanWei;

ConstraintMode.NO_CONSTRAINT:约束模式:没有约束

2、集合映射 @ElementCollection

(1)关系和元素集合
映射集合实际上存在三种可以存储的对象

  • 映射实体;
  • 可嵌入的集合;
  • 基本类型的集合.

当映射为一个包含目标实体类型的实例集合时,称之为一个多值关系。而可嵌入的集合和基本类型的集合不是关系,它们只是元素的集合。

区别:

  • 关系采用关系注解,@OneToMany、@ManyToMany
  • 元素集合采用@ElementCollection注解
@Embeddable
public class VacationEntry {
	@Temporal(TemporalType.DATE)
	private Calendar startDate;
	
	@Column(name="DAYS")
	private int daysTaken;
}

@Entity
public class Employee {
	@Id
	private int id;
	private long salary;
	
	@ElementCollection(targetClass=VacationEntry.class)
	private List<VacationEntry> vacationBookings;
	
	@ElementCollection
	private Set<String> nickNames;
}

注意

  • 可嵌入的集合类需要用@Embeddable 进行注解
  • 嵌入对象应该与引用它们的实体存储在同一个表中

元素集合需要一个单独的集合表(Collection table). 每个集合表必须有一个引用包含它的实体表的联结列。对于嵌入对象,结合表中的其他列用于映射可嵌入元素的特性,对于基本类型,只映射基本元素的状态即可.

使用@CollectionTable注解来指定集合表,它允许我们指定表的名称和联结列。

@Entity
public class Employee {
	@Id
	private int id;
	private long salary;
	
	@ElementCollection(targetClass=VacationEntry.class)
	@CollectionTable(name="VACATION",joinColumns=@JoinColumn(name="EMP_ID"))
	private List<VacationEntry> vacationBookings;
	
	@ElementCollection
	@Column(name="NICKNAME")
	private Set<String> nickNames;
}

参考文章:
[1] 集合映射

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值