知行合一--架构体系--persistence--Hibernate
目的:记录在项目中 repository 相关的实践思考和验证
决策点:
1. 粒度设计
2. 子类型问题
精要:
- 当你找不到一个好的自然键时,使用代理键(对应用程序用户不具有任何意义的主键列,不会呈现给应用程序的键,其存在的唯一目的就是标识应用程序内部的数据。
- 持久化的上下文中,id 与系统如何处理缓存和事务密切相关。
- 使用SQL对关系数据进行有效访问通常需要在相关的表之间使用联结。检索数据时所设计的表数量将确定可以在内存中导航的对象网络的深度。
- 首先访问关联时才提取关联实例数据的功能,称为延迟加载,这一数据访问的零碎方式在SQL 数据库的执行上下文中,没有效率,需要为每一个访问的节点或者对象网络的集合执行一条语句。N+1 查询问题。
1. 粒度设计
不一定非要重新定义一个新的 Address 表。



2. 子类型问题 继承 多态
一个外键约束会准确地引用一张目标表,定义一个引用多表的外键不简单。
对可更新视图的有限完整性规则。
JPA
该类可以是抽象的,如有必要,可以扩展一个非持久化类或实现一个接口,
它必须是一个顶层类,不嵌套在另一个类中。可持久化类及其所有方法都不能是 final ( JPA 规范要求)
Hibernate 会使用一个无参构造函数上的 Java 反射 API 来调用类,以便创建实例,该构造函数可以是不公开的。
但如果为了优化性能, Hibernate 要使用运行时生成的代理,则它必须是包可见的。
Hibernate 不需要访问器方法。
自定义类型转换器。对类字段的组合转换。
脏检查,是根据值--- 而不是对象标识 来比较,以确定该属性的持久化状态是否需要更新。
重要特例: 集合是通过标识来对比的。
对于映射为一个持久化集合的属性,应该从获取方法中准确返回与Hibernate传递给设置方法的实例相同的集合实例。
否则,Hibernate将更新数据库,即使不需要,每次保留在内存 中的状态都会与 数据库同步。
反例:
protected String[] names = new String[0];
public void setNames(List<String> names) {
this.name = names.toArray(new String[names.size()] );
}
public List<String> getNames(){
return Arrays.asList(names);
} // 如果Hibernate 要访问这些方法, 不要这么做!
如果Hibernate 直接访问names 字段,忽略 getter/setter ,则不会有问题。
异常处理:
当 加载和存储 实例且抛出一个 不受检测 的 RuntimeException, 如果Hibernate 使用访问器方法,则当前事务会回滚,且该异常需要
在 Java 持久化或Hibernate 原生 的 API 代码中进行处理。
如果抛出一个 受检测 应用程序异常, Hibernate 会将其包装到一个 RuntimeException中.
JPA 要求 属于集合类型属性的接口:
Java.util.Set, java.util.List, java.util.Collection. 基于接口编程。


从数据存储检索数据(在查询中)并且/或者 显示给用户(在Java代码中)时对数据排序,而不是在存储数据时 排序。
注意数据完整性,在有双向链接时特别注意:




message 对用于国际化的外部属性文件来说, 是关键的内容.
注解字段,验证引擎将直接访问这些字段。
如果选择通过访问器方法调用,则要注解带有验证约束的获取方法而非设置方法。
之后约束会成为类的API的一部分,且包含在其 Javadoc 中,让域 模型的实现更易于理解。
注意这是独立于通过JPA提供程序访问权的:Hibernate 验证器可以调用访问器方法,而Hibernate ORM 可以直接调用字段.

2021-04-18
@Id
private Long Id;
Hibernate 会使用该字段而非获取方法或设置方法来访问标识符的属性值。 由于@Id 在一个字段上,则JPA 提供程序会将直接访问这个类的字段,且默认
会将所有字段看成持久化状态的一部分。
字段访问通常时最佳选择。
配置键生成器 , p61
动态SQL 生成
默认,启动期间,Hibernate 会在创建持久化单元时为每一个持久化类创建SQL 语句。这些语句都是用于读取单行,删除一行等的CRUD,
预先在内存中存储这些操作,系统开销会耕地,而非在运行时每次必须执行如此简单的查询时生成SQL字符串。
如果只有少量语句,在JDBC级别准备好的语句缓存会更高效.
非常大的实体数量会影响启动实践,如果必须为数千个实体缓存一系列语句的话,这一查询语句缓存的内存消耗也将很高。
要在启动时禁用 INSERT 和 UPDATE 语句的生成,

在需要时而非预先生成 SQL 字符串。
UPDATE 将仅包含具有更新后的值得列,
INSERT 仅包含非空列。
让实体不可变
一个特定类的实例可以是不可变的。Bid 是不可变的 。
Hibernate 永远不需要对 BID 表执行 UPDATE 语句。当映射不可变类时避免脏检查。

当不能在你的数据库架构中创建一个视图时,可以将不可变的实体类 映射到一个 SQL select 查询.
将一个实体映射到子查询
无法修改数据库架构,无法添加一个新的视图。
目的:创建包含一个拍卖Item 标识符和对该商品的出价次数的视图。
使用Hibernate注解,创建一个应用程序级别的视图,以及一个映射到 Select 的只读实体类.



@Embeddable 类注解, 或属性本身,
映射为所属类得可嵌入组件,例如 类得 嵌入式类 Address 和 MonetaryAmount 两个可嵌入类。
会继承默认设置或者其所属根实体类明确生命过得访问策略。
@Access 注解,重写,如果在类/实体级别设置,HB会根据所选策略访问类的所有属性,要设置所有其他映射注解,包括 @Id,
分别在 字段 或 getter 方法上设置。
可重写单个属性的访问策略。 p76
noop 属性访问器, 在查询中引用虚属性,有一个只想在JPA 查询中使用的数据库列。 使用 Hbm.xml 原生元数据文件,Item类上的任何注解现在都被忽略。
PropertyAccessor 接口来定义自定义属性访问策略.
派生属性的值,是在运算时通过估算由 @org.hibernate.annotations.Formula 注解声明的SQL 表达式计算的
@org.hibernate.annotations.Formula( " substr(DESCRIPTION, 1, 12 ) || "..." )
protected String shortDescription;
@org.hibernate.annotations.Formula("(select av(b.AMOUNT) from BID b where b.ITEM_ID = ID)")
protected BigDecimal averageBidAmount;
在每次从数据库检索Item 实体时估算,其他任何时候都不估算。如果其他属性修改了,结果可能过期。
这些属性绝不会出现在SQL 的 Insert 或 Update 语句中,只会出现在Select 中,估算是发生在数据库中的.
列转换器: 允许编写 用于读取和写入 属性值的自定义SQL 表达式。
例如,将 kg 单位转换为 磅 单位, 可以使用Hibernate 扩展来实现转换: ColumnTransformer
生成的以及默认的属性值
首次插入行时,时间戳,商品的默认价格,以及为每次修改所运行的触发器.




避免 null, 不应使用可为空的列并且避免三元逻辑。
如果Address 需要对比,要注意重写 equals() hashCode() 。 且通过值来对比实例. 

所有的嵌入属性,无论它们在组合中的位置有多深,都会被映射到所属实体表的列---这里就是 Users 表
可以在任何级别声明 @AttributeOverrides, 正如为 City 类的 name 属性所做的那用,将它映射到 CITY 列
Address 中的 @AttributeOverride 或 根实体类 User 中的重写来实现。
嵌套属性可以使用圆点符号来引用:
User#address 上, @AttributeOverride(name = "city.name") 引用了 Address 的 #City#name 属性
甚至可以映射组件的集合 或者 组件到实体的引用.
big_integer java.math.BigInteger NUMERIC
Zipcode 两个子类。



如果单个可嵌入属性需要几个@Convert 注解,要转换 Address 的几个属性,可以将它们分组到一个@Converts 注解中。
还可以将转换器应用到集合与映射的值, 如果它们的值和/或键是基本类型或可嵌入类型。
可以在持久化Set<Zipcode> 上添加@Convert注解。 @ElementCollection 映射持久化集合。
持久化映射 @Convert 的 arrtibute 特殊语法 p97
JPA 转换器约束
1. 不能将它们应用到实体的标识符或版本属性。
2.如果希望为 枚举 或日期时间属性应用自定义转换器,不要使用 @Enumerated 或 @Temporal 注解
3. 可以在hbm.xml 文件将转换器应用到属性映射,但必须为其名称添加前缀 type="converter: qualified.ConverterName"
JPA hibernate 批量保存数据
reference : https://www.cnblogs.com/wenq001/p/9264861.html
# Mysql config spring.datasource.url=jdbc:mysql://localhost:3306/dddrepository?userUnicode=true&characterEncoding=utf-8&serverTimezone=GMT spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.username=guest123 spring.datasource.password=guest123 # Hibernate spring.jpa.properties.hibernate.jdbc.fetch_size=250 spring.jpa.properties.hibernate.jdbc.batch_size=50 # cp hikari spring.datasource.hikari.maximum-pool-size=50 spring.datasource.hikari.minimum-idle=2 spring.datasource.hikari.leak-detection-threshold=20000 spring.datasource.hikari.connection-timeout=60000
1 @Configuration 2 @EnableAutoConfiguration 3 public class SpringHibernateConfig { 4 @Bean 5 public LocalSessionFactoryBean sessionFactoryBean(DataSource dataSource){ 6 LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean(); 7 sessionFactoryBean.setDataSource(dataSource); 8 sessionFactoryBean.setPackagesToScan("com.srdc.dataplatform.dataprocess"); 9 sessionFactoryBean.setHibernateProperties(hibernateProperties()); 10 return sessionFactoryBean; 11 } 12 13 private Properties hibernateProperties(){ 14 Properties properties = new Properties(); 15 properties.put("hibernate.dialect","org.hibernate.dialect.MySQLDialect"); 16 properties.put("hibernate.form_sql",true); 17 properties.put("hibernate.use_sql",true); 18 properties.put("hibernate.show_sql",true); 19 properties.put("hibernate.max_fetch_depth",3); 20 properties.put("hibernate.jdbc.batch_size",10); 21 properties.put("hibernate.jdbc.fetch_size",50); 22 return properties; 23 } 24 }



2021-05-16


浙公网安备 33010602011771号