ORM规范: JPA
Posted 长不大的大灰狼
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ORM规范: JPA相关的知识,希望对你有一定的参考价值。
ORM规范: JPA
JPA(Java Persistence API)未提供ORM实现,只制订了一些规范,提供了一些编程的API接口,具体实现需要由服务厂商实现。
使用JPA规范进行数据库操作,底层需要hibernate作为实现类完成数据持久化工作:
一、基本使用
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);
使用 @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();
5、方法命名规则查询
根据方法的名字,创建查询
//方法命名方式查询(根据客户名称查询客户)
public Customer findByCustName(String custName);
Keyword | Sample | JPQL |
---|---|---|
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | findByFirstnameIs, findByFirstnameEquals | … where x.firstname = ?1 |
Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age ⇐ ?1 |
GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
After | findByStartDateAfter | … where x.startDate > ?1 |
Before | findByStartDateBefore | … where x.startDate < ?1 |
IsNull | findByAgeIsNull | … where x.age is null |
IsNotNull,NotNull | findByAge(Is)NotNull | … where x.age not null |
Like | findByFirstnameLike | … where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1 (parameter bound with appended %) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1 (parameter bound with prepended %) |
Containing | findByFirstnameContaining | … where x.firstname like ?1 (parameter bound wrapped in %) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection age) | … where x.age not in ?1 |
TRUE | findByActiveTrue() | … where x.active = true |
FALSE | findByActiveFalse() | … where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | … 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.getUsersJPA会是ORM的王道吗