定义
- JPA:Java Persistence API, 一套规范, 用于对象持久化的API. 内部由接口和抽象类组成 (通过 JDK 5.0 注解描述对象和表的映射关系),底层需要 Hibernate 作为其实现类完成数据持久化工作.
- Hibernate是一套成熟的 ORM 开放源代码的对象关系映射框架,将 POJO 与数据库表建立映射关系,而且 Hibernate 实现了 JPA 规范,所以也可以称 Hibernate 为 JPA的一种实现方式,我们使用 JPA 的 API 编程,意味着站在更高的角度上看待问题(面向接口编程)。Hibernate 可以自动生成 SQL 语句,自动执行,使得 Java 程序员可以随心所欲的使用对象编程思维来操纵数据库
- Spring Data JPA是 Spring 提供的一套对 JPA 操作更加高级的封装,是在 JPA 规范下的专门用来进行数据持久化的解决方案
使用
-
扫描
Entity 类、Repository 类会被自动扫描到并注册到 Spring 容器, 此时无需额外配置. 当不在同一包或不在子包下时, 需要分别通过在启动类上加注解 @EntityScan(basePackages = {“xxx.xxx”}) 允许多处有交集、@EnableJpaRepositories(basePackages = {“xxx.xxx”}) 允许多处不能交集 来分别指定 Entity、Repository 类的包名 -
JPA 不用
@Repository
- Spring 通过扩展 Repository 接口来识别存储库, 接口仅仅是一个标识, 表明任何继承它的均为仓库接口类
- @NoRepositoryBean 注释是为了防止 Spring 将 Repository 扩展接口(如 CrudRepository) 视为存储库
- 最好搭配 @Resource 注解注入
spring 有两种方式识别dao层,一种是注解的 @Repository,一种是继承 Repository 接口.
然而, 大多数框架都有自己的一套扫描规则, 实际不用使用 @Repository, 如 Mybatis 有@MapperScanner 扫描后自动注入
实体类注解, Entity 注解代表一个实体, 用于 JPA (没加不会被 JPA 识别); Table 注解只用来描述表的详细信息 (Entity marks the class as a JPA entity, Table specifiess the details of the table that the entity will be mapped to, such as the table name and schema. JPA will use the class name as the table name by default.)
@Entity
:指定当前类为实体类- name: 实体名, 表名
@Table
:指定实体类与数据库表映射关系 (可省略)- schema:数据库名
- name:表名 (Optional) The name of the table. Defaults to the entity name
合并: @Entity(name = "table_name")
, 在 JPA 中 @Entity
比较重要, 其他框架比如 tk.mapper 则复用了 @Table
注解;
实体类属性
- @Id:当前主键字段
- @GeneratedValue:
- strategy:主键生成策略
- GenerationType.AUTO:auto_increment
- GenerationType.IDENTITY:表自增长,不支持Oracle
- GenerationType.SEQUENCE:序列生成主键,不支持MySQL
- GenerationType.TABLE:数据库表生成主键,框架借由表模拟序列生成主键
- strategy:主键生成策略
- @Column:建立实体类属性与数据库表字段映射关系,无此字段也会将字段映射到表列。当实体的属性与其映射的数据库表的列不同名时需要使用 @Column 标注说明
- name:指定映射的数据库表字段
- unique:是否唯一
- nullable:是否为空
- insertable:是否可插入
- updatable:是否可更新
- @Convert(converter = UserStatus.class):Entity中将任意对象映射为一个数据库字段
JPA命名查询原理
- 方法名解析原理:方法名中除了保留字(findBy、top、within等)外的部分以 and 为分隔符提取出条件单词,然后解析条件获取各个单词并看是否和 Entity 中的属性对应(不区分大小写进行比较)
get/find 与 by之间的字段会被忽略:getNameById == getById,会根据 id 查出整个Entity而不会只查 name 字段
JPA的 select 操作
Repository 中 @Query
JPA的 update 操作
Repository 中 @Modifying + @Query
JpaRepository
findOne()与getOne():findOne立即加载;getOne延迟加载
表关系
- 一对一:@OneToOne
- 一对多:@OneToMany
@OneToMany(targetEntity = LinkMan.class)
@JoinColumn(name = "外键名称" referencedColumnName = "cust_id")
private Set<LinkMan> linkMans = new HashSet<>();
- 多对一:@ManyToOne
@ManyToOne(targetEntity = Customer.class)
@JoinColumn(name = "外键名称" referencedColumnName = "cust_id")
private Customer customer;
@JoinColumn/@PrimaryKeyJoinColumn、@MapsId
-
@JoinColumn用来指定外键,其name属性指定该注解所在Entity对应的表的一个列名
-
外键属性不是主键的场景(第一种),用 @OneToOne/@ManyToOne + @JoinColumn 即可,为了简洁推荐不用@MapIds,示例见上面的school_id关联school id设置
-
外键属性是主键的场景(第二种),用 @OneToOne + @JoinColumn + @MapsId ,示例见上面的student id关联user id设置
-
在一对多或一对一的关系映射中,如果不表明mappedBy属性,默认是由本方维护外键。
-
将维护权交给多的一方. 在一的一方配置上 mappedBy 属性,将维护权交给多的一方来维护
条件查询
- 模糊查询
对于单字段的可以直接在方法名加Containing
@Query("select s from SchoolEntity s where s.customerId=?1 and (?2 is null or s.name like %?2% or s.bz like %?2% ) ")
List<User> getByUserId(String userId, Pageable pageable);
- In查询
@Query( "select * from student where id in ?1", nativeQuery=true)
//@Query( "select s from StudentEntity s where s.id in ?1")
List<StudentEntity> myGetByIdIn(Collection<String> studentIds );//复杂查询,自定义查询逻辑
List<StudentEntity> getByIdIn( Collection<String> studentIds );//简单查询,声明语句即可
级联操作
用于有依赖关系的实体间(@OneToMany、@ManyToOne、@OneToOne)的级联操作:当对一个实体进行某种操作时,若该实体加了与该操作相关的级联标记,则该操作会传播到与该实体关联的实体(即对被级联标记的实体施加某种与级联标记对应的操作时,与该实体相关联的其他实体也会被施加该操作)。包括:
@OneToMany(targetEntity = LinkMan.class, cascade = CascadeType.ALL)
@JoinColumn(name = "外键名称" referencedColumnName = "cust_id")
private Set<LinkMan> linkMans = new HashSet<>();
- CascadeType.PERSIST:持久化,即保存
- CascadeType.REMOVE:删除当前实体时,关联实体也将被删除
- CascadeType.MERGE:更新或查询
- CascadeType.REFRESH:级联刷新,即在保存前先更新别人的修改:如Order、Item被用户A、B同时读出做修改且B的先保存了,在A保存时会先更新Order、Item的信息再保存。
- CascadeType.DETACH:级联脱离,如果你要删除一个实体,但是它有外键无法删除,你就需要这个级联权限了。它会撤销所有相关的外键关联
- CascadeType.ALL:上述所有
注:级联应该标记在One的一方 。如对于 @OneToMany的Person 和 @ManyToOne的Phone,若将- CascadeType.REMOVE标记在Phone则删除Phone也会删除Person,显然是错的。慎用CascadeType.ALL,应该根据业务需求选择所需的级联关系,否则可能酿成大祸
@MappedSuperclass
@MappedSuperclass一般标记在父类上,用来标识父类
基于代码的重复使用和模型分离思想,将实体类的多个属性分别封装到不同的非实体类中。将需要频繁使用的字段封装到非实体类中,调用时直接继承. 例如编号ID,创建者,创建时间,修改者,修改时间,备注等,如需调用再继承该非实体类;
- 标注为 @MappedSuperclass 的类将不是一个完整的实体类 (由于存在包扫描规则, 就算未标注 @Table 或 @Entity 也会被加载为实体类),就不会映射到数据库表,其属性都将映射到子类的数据库字段中
- 标注为 @MappedSuperclass 的类不能再标注 @Entity 或 @Table 注解,也无需实现序列化接口
- 但是如果一个标注为 @MappedSuperclass 的类继承了另外一个实体类或者另外一个同样标注了 @MappedSuperclass 的类的话,他将可以使用@AttributeOverride 或 @AttributeOverrides 注解重定义其父类的属性映射到数据库表中的字段 (无论是否是实体类) 例如, 重定义字段名或长度等属性,使用 @AttributeOverride 中的子属性 @Column 进行具体的定义
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@Table
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class User extends Tenant implements Cloneable {
}
Inheritance Type | Description | Pros | Cons |
---|---|---|---|
SINGLE_TABLE | All classes mapped to a single table with a discriminator column. | Simplicity and performance (fewer joins). | Wide table with many nullable columns. |
TABLE_PER_CLASS | Each class has its own table, including inherited fields. | Clear separation of data; no null values. | Redundant data and complex queries on superclass. |
JOINED | Each class has its own table; tables are linked by foreign keys. | Reduced redundancy; normalized structure. | More complex queries; potential performance overhead from joins. |
Example Structure
Strategy | Animal Table | Dog Table | Cat Table |
---|---|---|---|
JOINED | id , name | id (FK) , breed | id (FK) , isIndoor |
SINGLE_TABLE | id , name , breed , isIndoor | N/A | N/A |
TABLE_PER_CLASS | N/A | id , name , breed | id , name , isIndoor |
Hibernate 中 entity 的四种状态
为了兼顾性能和可靠性
- Transient (瞬时状态)
- Persistent (持久化状态)
- Detached (游离状态)
- Removed (删除状态)
.NET 的 ORM EF 也是多种状态
Detached
Added
Unchanged
Modified
Deleted
https://learn.microsoft.com/en-us/ef/core/change-tracking/#entity-states
Entity states
Every entity is associated with a given EntityState:
- Detached entities are not being tracked by the DbContext.
- Added entities are new and have not yet been inserted into the database. This means they will be inserted when SaveChanges is called.
- Unchanged entities have not been changed since they were queried from the database. All entities returned from queries are initially in this state.
- Modified entities have been changed since they were queried from the database. This means they will be updated when SaveChanges is called.
- Deleted entities exist in the database, but are marked to be deleted when SaveChanges is called.
EF Core tracks changes at the property level. For example, if only a single property value is modified, then a database update will change only that value. However, properties can only be marked as modified when the entity itself is in the Modified state. (Or, from an alternate perspective, the Modified state means that at least one property value has been marked as modified.)
The following table summarizes the different states:
Entity state | Tracked by DbContext | Exists in database | Properties modified | Action on SaveChanges |
---|---|---|---|---|
Detached | No | - | - | - |
Added | Yes | No | - | Insert |
Unchanged | Yes | Yes | No | - |
Modified | Yes | Yes | Yes | Update |
Deleted | Yes | Yes | - | Delete |
ORM 思路
ORM 是不限语言的一种思路,核心思想是想用面向对象来取代关系运算 (虽然它名字叫 “对象关系映射”) 所以用面向对象的思路去思考它,暂时把关系运算的思路放一边比较好
另外 ORM 的思路从现在看是失败的,因为对象关系并不能真的替换掉关系运算,所以 ORM 和 SQL 之间存在阻尼,这就是用起来各种不舒服的核心所在。现存的 ORM 也就剩下 java 的 hibernate 和 .net EF (Entity Framework) 了,其它的认真来说都不能算 ORM ,是基于 active record 思路(创始者是 ruby on rail)的 sql 强化工具。对关系数据库的访问方式最终还是回归到 sql 本身
另外,最近国内又有一些作者开始折腾了,但是他们的思路也不是 ORM 的思路,它们的思路是希望通过链式 api 调用,使其能映射几乎绝大部分 SQL 的想法,老实说不是很看好,因为复杂 sql 最合适的思路还是直接写 sql
总之,ORM 的核心是 O, 而不是 R, 但是这个思路没走通, 之后的绝大部分关系数据访问库, 都倾向于 R, 分歧不过是怎么把用 R 的这个过程搞的漂亮点