Chapter 11. Working with objects https://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/objectstate.html
第 11 章 与对象共事 http://itmyhome.com/hibernate/objectstate.html
Hibernate 对象有三种状态:
Transient 临时态/瞬时态,指从对象通过 new 语句创建到被持久化之前的状态,此时对象不在 Session 的缓存中,不与任何 Session 实例相关联,在数据库中没有与之对应的记录,主键 id 为 null。
Transient
如何获得临时态对象? 1、通过new语句创建新对象。 2、执行对象的 delete() 方法,对于游离状态的对象,delete() 方法会将其与数据库中对应的记录删除;而对于持久化状态的对象,delete() 方法会将其与数据库中对应的记录删除并将其在 Session 缓存中删除。
Persistent 持久态,是指从 对象被持久化 到 Session 对象被销毁 之前的状态,此时对象在 Session 的缓存中,与 Session 实例相关联,在数据库中有与之对应的记录,主键 id 不为 null。 注意: Session 在清理缓存的时候,会根据持久化对象的属性变化更新数据库
Persistent
如何获得持久态对象? 1、临时对象调用 save() 或 update() 等持久化方法后会变为持久态对象。 2、执行 load(), findOne() 或 findAll() 等查询方法返回的都是持久态对象。
Detached 游离态/脱管态,是指从 持久化对象的Session对象被销毁 到 该对象消失 之前的状态,此时对象不在 Session 的缓存中,不与任何 Session 实例相关联,但在数据库中有与之对应的记录,id 不为 null。
Detached
如何获得游离态对象? 1、调用 close/clear 方法清理 session 中的缓存对象,则所有的缓存对象都会变为游离态。 2、调用 session.evict() 方法,从缓存中删除一个持久态对象,则此对象变为游离态。
脱管(Detached)对象如果重新关联到某个新的 Session 上, 会再次转变为持久(Persistent)的(在Detached其间的改动将被持久化到数据库)。
可通过 session 中是否包含 entity session.contains(entity) 方法判断对象是否持久态,在 session 中就是持久态。 对于非持久态对象,可通过是否有主键id判断是否游离态,无id的是临时态,有id的是游离态。
session.contains(entity)
另外, SessionImpl.contains() 内有 persistenceContext.getEntry(object).getStatus() 获取对象状态(MANAGED,READ_ONLY,DELETED,GONE,LOADING,SAVING),但外部无法访问。
SessionImpl.contains()
persistenceContext.getEntry(object).getStatus()
例1、 持久化对象上的更新操作在事务提交/flush()后会被更新到数据库
@Test public void testExample1() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); // new 对象时临时态 userDO.setName("小明"); session.save(userDO); // save 后变为持久态,id被填充 userDO.setName("李华"); session.getTransaction().commit(); // 事务结束时,持久化对象的属性变化会被自动更新到数据库 注意:(1)执行完save后还未到事务提交,就会触发insert sql,实体也就有了id(由于事务还未提交,在其他Session中看不到这个数据) save之后实体就有了ID (2)最后一次在持久化对象上 setName 更新后,虽然没有执行 save 操作,但事务结束 commit 时也会将更新保存到数据库,事务提交时会发出一条更新sql,总共发出两条sql: Hibernate: insert (address, birthday, has_children, name) values (?, ?, ?, ?) Hibernate: update address=?, birthday=?, has_children=?, name=? where 多次更新只最后提交时发出sql例2、持久态对象在事务提交前,无论显示执行多少次 save/update 操作,hibernate 都不会发送 sql 语句,只有当事物提交/flush()的时候 hibernate 才会拿当前这个对象与之前保存在session 中的持久化对象进行比较,如果不相同就发送一条 update 的 sql 语句,否则就不会发送 update 语句 @Test public void testExample2() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); // new 对象是临时态 userDO.setName("小明"); session.save(userDO); // save 后变为持久态 userDO.setName("李华"); session.save(userDO); // 不会立即执行 insert 语句 userDO.setAddress("北京市"); session.update(userDO); session.getTransaction().commit(); // 事务结束时发出 insert 和 update 两条语句 第一个 save() 执行完会发出 insert sql,然后后面的 save 和 update 都不会再发出sql,直到最后事务提交时会发出一条 update sql,总共两条sql: Hibernate: insert (address, birthday, has_children, name, id) values (?, ?, ?, ?, ?) Hibernate: update address=?, birthday=?, has_children=?, name=? where 事务提交后持久态对象的修改自动更新到数据库例3、 @Test public void testExample3() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); userDO.setName("姓名改"); session.getTransaction().commit(); // 最后提交事务时对持久态对象的修改会被更新到数据库 之前一段类似下面的代码,忘了写 save() 语句,但发现还是成功更新了user的name,不知道为什么 public void updateUser(req) { UserDO user = jpaRepository.findOne(1L); user.setName(req.getName()); 事务结束时,Hibernate 会自动保存更新了的 DO,在了解 Hibernate 对象状态前不知道为什么,查问题才查到 Hibernate 对象状态相关信息。 https://stackoverflow.com/questions/12859305/hibernate-how-to-disable-automatic-saving-of-dirty-objectshttps://stackoverflow.com/questions/8190926/transactional-saves-without-calling-update-method 游离态对象例4、游离态对象 @Test public void testExample4() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); session.clear(); // 清空 session,user变为游离态 userDO.setName("姓名改"); System.out.println("Entity在当前Session中:" + session.contains(userDO)); session.getTransaction().commit(); // 提交事务时无需更新 调用 session.clear() 方法会将 session 的缓存对象清空,那么 session 中就没有了 user 这个对象,这个时候在提交事务的时候,发现 session 中已经没有该对象了,所以就不会进行任何操作,对象的修改也不会更新到数据库。session.contains(userDO) 返回 false,游离态对象不在 session 中。 持久态对象无法修改ID例5、如果试图修改一个持久化对象的ID值的话,就会抛出异常javax.persistence.PersistenceException: org.hibernate.HibernateException: identifier of an instance of com.masikkk.common.jpa.UserDO was altered from 1 to 22 @Test public void testExample5() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); userDO.setId(22L); // 运行时会抛异常 session.getTransaction().commit(); https://www.cnblogs.com/xiaoluo501395377/p/3380270.htmlhttps://www.baeldung.com/hibernate-session-object-states Hibernate 缓存一级缓存(Session级缓存)一级缓存的生命周期和 Session 一致,也称为 Session 级缓存,或者事务级缓存。一级缓存时默认开启的,不可关闭。 查询:当在同一个 Session 中查询同一个对象时,Hibernate 不会再次与数据库交互,直接从 Session 缓存中取。更新:save/update 更新对象时,Hibernate 不会立即发出 SQL 将这个数据存到数据库,而是将它放在了 Session 的一级缓存中,直到调用 flush() 或提交事务时才会一并存到数据库。 例1、下面两次根据ID查询user,只会执行一次select语句,第二次会从 Session 缓存中获取 @Test public void testSessionCache1() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); System.out.println(session.get(UserDO.class, 1L)); // 有缓存,只执行一次查询 例2、HQL 查询不使用 Session 缓存 @Test public void testSessionCache2() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); // HQL 查询不走缓存,还是会执行sql Query idQuery = session.createQuery("from UserDO where id = :id"); idQuery.setParameter("id", 1L); List<UserDO> userDOList = idQuery.list(); userDOList.forEach(System.out::println); // 再执行一次 HQL 查询还是不走缓存,会再执行一次sql userDOList = idQuery.list(); 例3、虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 @Test public void testSessionCache3() { Session session = HibernateUtils.openSession(); Query nameQuery = session.createQuery("from UserDO where name = :n"); nameQuery.setParameter("n", "李华6"); List<UserDO> userDOList = nameQuery.list(); userDOList.forEach(System.out::println); // 虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 System.out.println(session.get(UserDO.class, 6L)); 二级缓存(应用级缓存)二级缓存是全局性的,可以在多个 Session 中共享数据,二级缓存称为是 SessionFactory 级缓存,或应用级缓存。二级缓存默认没有开启,需要手动配置缓存实现(EhCache等)才可以使用。开启二级缓存后,根据id查询数据时,会先在一级缓存查询,没有再去二级缓存,还没有再去数据库查。在 Spring Data 中可通过 javax.persistence.Cacheable 等注解使用。 注意:一级缓存和二级缓存都是只在根据 ID 查询对象时起作用,并不对全部查询生效。 例1、默认二级缓存是不开启的,两个Session分别查id=1的数据,会执行两次sql @Test public void testSecondLevelCache() { Session session1 = HibernateUtils.openSession(); System.out.println(session1.get(UserDO.class, 1L)); Session session2 = HibernateUtils.openSession(); System.out.println(session2.get(UserDO.class, 1L)); 开启二级缓存1、打开二级缓存开关,测试过程中发现 use_second_level_cache 开关默认是开启的 <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.internal.EhCacheRegionFactory</property> 或在 properties 文件中配置: hibernate.cache.use_second_level_cache=true hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory 或者在 Spring-Boot yml 配置: spring: properties: hibernate: cache: use_second_level_cache: true region: factory_class: org.hibernate.cache.jcache.JCacheRegionFactory provider_configuration_file_resource_path: ehcache.xml 2、二级缓存需要在各个实体上单独开启,注解需要二级缓存的实体 import javax.persistence.Cacheable; import org.hibernate.annotations.CacheConcurrencyStrategy; @Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") private long id; 缓存并发策略READ_ONLY 只适用于只读的实体/表NONSTRICT_READ_WRITE 事务提交后会更新缓存,非强一致性,可能读到脏数据,适用于可容忍一定非一致性的数据。READ_WRITE 保证强一致性,缓存实体被更新时会加一个软锁,更新提交后软锁释放。访问加了软锁的缓存实体的并发请求会直接从数据库取数据。TRANSACTIONAL 缓存与数据库数据之间加 XA 分布式事务,保证两者数据一致性。 Hibernate Second-Level Cachehttps://www.baeldung.com/hibernate-second-level-cache 查询(Query)缓存Hibernate 的一、二级缓存都是根据对象 id 来查找时才起作用,如果需要缓存任意查询条件,就需要用到查询缓存。 Hibernate 配置开启 query 缓存 <property name="hibernate.cache.use_query_cache">true</property> 使用 Spring-Data-Jpa 时可在 application.yml 中配置开启 spring: properties: hibernate: cache: use_query_cache: true Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
@Test public void testExample1() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); // new 对象时临时态 userDO.setName("小明"); session.save(userDO); // save 后变为持久态,id被填充 userDO.setName("李华"); session.getTransaction().commit(); // 事务结束时,持久化对象的属性变化会被自动更新到数据库 注意:(1)执行完save后还未到事务提交,就会触发insert sql,实体也就有了id(由于事务还未提交,在其他Session中看不到这个数据) save之后实体就有了ID (2)最后一次在持久化对象上 setName 更新后,虽然没有执行 save 操作,但事务结束 commit 时也会将更新保存到数据库,事务提交时会发出一条更新sql,总共发出两条sql:
注意:(1)执行完save后还未到事务提交,就会触发insert sql,实体也就有了id(由于事务还未提交,在其他Session中看不到这个数据)
(2)最后一次在持久化对象上 setName 更新后,虽然没有执行 save 操作,但事务结束 commit 时也会将更新保存到数据库,事务提交时会发出一条更新sql,总共发出两条sql:
Hibernate: insert (address, birthday, has_children, name) values (?, ?, ?, ?) Hibernate: update address=?, birthday=?, has_children=?, name=? where 多次更新只最后提交时发出sql例2、持久态对象在事务提交前,无论显示执行多少次 save/update 操作,hibernate 都不会发送 sql 语句,只有当事物提交/flush()的时候 hibernate 才会拿当前这个对象与之前保存在session 中的持久化对象进行比较,如果不相同就发送一条 update 的 sql 语句,否则就不会发送 update 语句 @Test public void testExample2() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); // new 对象是临时态 userDO.setName("小明"); session.save(userDO); // save 后变为持久态 userDO.setName("李华"); session.save(userDO); // 不会立即执行 insert 语句 userDO.setAddress("北京市"); session.update(userDO); session.getTransaction().commit(); // 事务结束时发出 insert 和 update 两条语句 第一个 save() 执行完会发出 insert sql,然后后面的 save 和 update 都不会再发出sql,直到最后事务提交时会发出一条 update sql,总共两条sql: Hibernate: insert (address, birthday, has_children, name, id) values (?, ?, ?, ?, ?) Hibernate: update address=?, birthday=?, has_children=?, name=? where 事务提交后持久态对象的修改自动更新到数据库例3、 @Test public void testExample3() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); userDO.setName("姓名改"); session.getTransaction().commit(); // 最后提交事务时对持久态对象的修改会被更新到数据库 之前一段类似下面的代码,忘了写 save() 语句,但发现还是成功更新了user的name,不知道为什么 public void updateUser(req) { UserDO user = jpaRepository.findOne(1L); user.setName(req.getName()); 事务结束时,Hibernate 会自动保存更新了的 DO,在了解 Hibernate 对象状态前不知道为什么,查问题才查到 Hibernate 对象状态相关信息。 https://stackoverflow.com/questions/12859305/hibernate-how-to-disable-automatic-saving-of-dirty-objectshttps://stackoverflow.com/questions/8190926/transactional-saves-without-calling-update-method 游离态对象例4、游离态对象 @Test public void testExample4() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); session.clear(); // 清空 session,user变为游离态 userDO.setName("姓名改"); System.out.println("Entity在当前Session中:" + session.contains(userDO)); session.getTransaction().commit(); // 提交事务时无需更新 调用 session.clear() 方法会将 session 的缓存对象清空,那么 session 中就没有了 user 这个对象,这个时候在提交事务的时候,发现 session 中已经没有该对象了,所以就不会进行任何操作,对象的修改也不会更新到数据库。session.contains(userDO) 返回 false,游离态对象不在 session 中。 持久态对象无法修改ID例5、如果试图修改一个持久化对象的ID值的话,就会抛出异常javax.persistence.PersistenceException: org.hibernate.HibernateException: identifier of an instance of com.masikkk.common.jpa.UserDO was altered from 1 to 22 @Test public void testExample5() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); userDO.setId(22L); // 运行时会抛异常 session.getTransaction().commit(); https://www.cnblogs.com/xiaoluo501395377/p/3380270.htmlhttps://www.baeldung.com/hibernate-session-object-states Hibernate 缓存一级缓存(Session级缓存)一级缓存的生命周期和 Session 一致,也称为 Session 级缓存,或者事务级缓存。一级缓存时默认开启的,不可关闭。 查询:当在同一个 Session 中查询同一个对象时,Hibernate 不会再次与数据库交互,直接从 Session 缓存中取。更新:save/update 更新对象时,Hibernate 不会立即发出 SQL 将这个数据存到数据库,而是将它放在了 Session 的一级缓存中,直到调用 flush() 或提交事务时才会一并存到数据库。 例1、下面两次根据ID查询user,只会执行一次select语句,第二次会从 Session 缓存中获取 @Test public void testSessionCache1() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); System.out.println(session.get(UserDO.class, 1L)); // 有缓存,只执行一次查询 例2、HQL 查询不使用 Session 缓存 @Test public void testSessionCache2() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); // HQL 查询不走缓存,还是会执行sql Query idQuery = session.createQuery("from UserDO where id = :id"); idQuery.setParameter("id", 1L); List<UserDO> userDOList = idQuery.list(); userDOList.forEach(System.out::println); // 再执行一次 HQL 查询还是不走缓存,会再执行一次sql userDOList = idQuery.list(); 例3、虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 @Test public void testSessionCache3() { Session session = HibernateUtils.openSession(); Query nameQuery = session.createQuery("from UserDO where name = :n"); nameQuery.setParameter("n", "李华6"); List<UserDO> userDOList = nameQuery.list(); userDOList.forEach(System.out::println); // 虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 System.out.println(session.get(UserDO.class, 6L)); 二级缓存(应用级缓存)二级缓存是全局性的,可以在多个 Session 中共享数据,二级缓存称为是 SessionFactory 级缓存,或应用级缓存。二级缓存默认没有开启,需要手动配置缓存实现(EhCache等)才可以使用。开启二级缓存后,根据id查询数据时,会先在一级缓存查询,没有再去二级缓存,还没有再去数据库查。在 Spring Data 中可通过 javax.persistence.Cacheable 等注解使用。 注意:一级缓存和二级缓存都是只在根据 ID 查询对象时起作用,并不对全部查询生效。 例1、默认二级缓存是不开启的,两个Session分别查id=1的数据,会执行两次sql @Test public void testSecondLevelCache() { Session session1 = HibernateUtils.openSession(); System.out.println(session1.get(UserDO.class, 1L)); Session session2 = HibernateUtils.openSession(); System.out.println(session2.get(UserDO.class, 1L)); 开启二级缓存1、打开二级缓存开关,测试过程中发现 use_second_level_cache 开关默认是开启的 <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.internal.EhCacheRegionFactory</property> 或在 properties 文件中配置: hibernate.cache.use_second_level_cache=true hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory 或者在 Spring-Boot yml 配置: spring: properties: hibernate: cache: use_second_level_cache: true region: factory_class: org.hibernate.cache.jcache.JCacheRegionFactory provider_configuration_file_resource_path: ehcache.xml 2、二级缓存需要在各个实体上单独开启,注解需要二级缓存的实体 import javax.persistence.Cacheable; import org.hibernate.annotations.CacheConcurrencyStrategy; @Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") private long id; 缓存并发策略READ_ONLY 只适用于只读的实体/表NONSTRICT_READ_WRITE 事务提交后会更新缓存,非强一致性,可能读到脏数据,适用于可容忍一定非一致性的数据。READ_WRITE 保证强一致性,缓存实体被更新时会加一个软锁,更新提交后软锁释放。访问加了软锁的缓存实体的并发请求会直接从数据库取数据。TRANSACTIONAL 缓存与数据库数据之间加 XA 分布式事务,保证两者数据一致性。 Hibernate Second-Level Cachehttps://www.baeldung.com/hibernate-second-level-cache 查询(Query)缓存Hibernate 的一、二级缓存都是根据对象 id 来查找时才起作用,如果需要缓存任意查询条件,就需要用到查询缓存。 Hibernate 配置开启 query 缓存 <property name="hibernate.cache.use_query_cache">true</property> 使用 Spring-Data-Jpa 时可在 application.yml 中配置开启 spring: properties: hibernate: cache: use_query_cache: true Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
Hibernate: insert (address, birthday, has_children, name) values (?, ?, ?, ?) Hibernate: update address=?, birthday=?, has_children=?, name=? where
多次更新只最后提交时发出sql例2、持久态对象在事务提交前,无论显示执行多少次 save/update 操作,hibernate 都不会发送 sql 语句,只有当事物提交/flush()的时候 hibernate 才会拿当前这个对象与之前保存在session 中的持久化对象进行比较,如果不相同就发送一条 update 的 sql 语句,否则就不会发送 update 语句 @Test public void testExample2() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); // new 对象是临时态 userDO.setName("小明"); session.save(userDO); // save 后变为持久态 userDO.setName("李华"); session.save(userDO); // 不会立即执行 insert 语句 userDO.setAddress("北京市"); session.update(userDO); session.getTransaction().commit(); // 事务结束时发出 insert 和 update 两条语句 第一个 save() 执行完会发出 insert sql,然后后面的 save 和 update 都不会再发出sql,直到最后事务提交时会发出一条 update sql,总共两条sql: Hibernate: insert (address, birthday, has_children, name, id) values (?, ?, ?, ?, ?) Hibernate: update address=?, birthday=?, has_children=?, name=? where 事务提交后持久态对象的修改自动更新到数据库例3、 @Test public void testExample3() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); userDO.setName("姓名改"); session.getTransaction().commit(); // 最后提交事务时对持久态对象的修改会被更新到数据库 之前一段类似下面的代码,忘了写 save() 语句,但发现还是成功更新了user的name,不知道为什么 public void updateUser(req) { UserDO user = jpaRepository.findOne(1L); user.setName(req.getName()); 事务结束时,Hibernate 会自动保存更新了的 DO,在了解 Hibernate 对象状态前不知道为什么,查问题才查到 Hibernate 对象状态相关信息。 https://stackoverflow.com/questions/12859305/hibernate-how-to-disable-automatic-saving-of-dirty-objectshttps://stackoverflow.com/questions/8190926/transactional-saves-without-calling-update-method 游离态对象例4、游离态对象 @Test public void testExample4() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); session.clear(); // 清空 session,user变为游离态 userDO.setName("姓名改"); System.out.println("Entity在当前Session中:" + session.contains(userDO)); session.getTransaction().commit(); // 提交事务时无需更新 调用 session.clear() 方法会将 session 的缓存对象清空,那么 session 中就没有了 user 这个对象,这个时候在提交事务的时候,发现 session 中已经没有该对象了,所以就不会进行任何操作,对象的修改也不会更新到数据库。session.contains(userDO) 返回 false,游离态对象不在 session 中。 持久态对象无法修改ID例5、如果试图修改一个持久化对象的ID值的话,就会抛出异常javax.persistence.PersistenceException: org.hibernate.HibernateException: identifier of an instance of com.masikkk.common.jpa.UserDO was altered from 1 to 22 @Test public void testExample5() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); userDO.setId(22L); // 运行时会抛异常 session.getTransaction().commit(); https://www.cnblogs.com/xiaoluo501395377/p/3380270.htmlhttps://www.baeldung.com/hibernate-session-object-states Hibernate 缓存一级缓存(Session级缓存)一级缓存的生命周期和 Session 一致,也称为 Session 级缓存,或者事务级缓存。一级缓存时默认开启的,不可关闭。 查询:当在同一个 Session 中查询同一个对象时,Hibernate 不会再次与数据库交互,直接从 Session 缓存中取。更新:save/update 更新对象时,Hibernate 不会立即发出 SQL 将这个数据存到数据库,而是将它放在了 Session 的一级缓存中,直到调用 flush() 或提交事务时才会一并存到数据库。 例1、下面两次根据ID查询user,只会执行一次select语句,第二次会从 Session 缓存中获取 @Test public void testSessionCache1() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); System.out.println(session.get(UserDO.class, 1L)); // 有缓存,只执行一次查询 例2、HQL 查询不使用 Session 缓存 @Test public void testSessionCache2() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); // HQL 查询不走缓存,还是会执行sql Query idQuery = session.createQuery("from UserDO where id = :id"); idQuery.setParameter("id", 1L); List<UserDO> userDOList = idQuery.list(); userDOList.forEach(System.out::println); // 再执行一次 HQL 查询还是不走缓存,会再执行一次sql userDOList = idQuery.list(); 例3、虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 @Test public void testSessionCache3() { Session session = HibernateUtils.openSession(); Query nameQuery = session.createQuery("from UserDO where name = :n"); nameQuery.setParameter("n", "李华6"); List<UserDO> userDOList = nameQuery.list(); userDOList.forEach(System.out::println); // 虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 System.out.println(session.get(UserDO.class, 6L)); 二级缓存(应用级缓存)二级缓存是全局性的,可以在多个 Session 中共享数据,二级缓存称为是 SessionFactory 级缓存,或应用级缓存。二级缓存默认没有开启,需要手动配置缓存实现(EhCache等)才可以使用。开启二级缓存后,根据id查询数据时,会先在一级缓存查询,没有再去二级缓存,还没有再去数据库查。在 Spring Data 中可通过 javax.persistence.Cacheable 等注解使用。 注意:一级缓存和二级缓存都是只在根据 ID 查询对象时起作用,并不对全部查询生效。 例1、默认二级缓存是不开启的,两个Session分别查id=1的数据,会执行两次sql @Test public void testSecondLevelCache() { Session session1 = HibernateUtils.openSession(); System.out.println(session1.get(UserDO.class, 1L)); Session session2 = HibernateUtils.openSession(); System.out.println(session2.get(UserDO.class, 1L)); 开启二级缓存1、打开二级缓存开关,测试过程中发现 use_second_level_cache 开关默认是开启的 <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.internal.EhCacheRegionFactory</property> 或在 properties 文件中配置: hibernate.cache.use_second_level_cache=true hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory 或者在 Spring-Boot yml 配置: spring: properties: hibernate: cache: use_second_level_cache: true region: factory_class: org.hibernate.cache.jcache.JCacheRegionFactory provider_configuration_file_resource_path: ehcache.xml 2、二级缓存需要在各个实体上单独开启,注解需要二级缓存的实体 import javax.persistence.Cacheable; import org.hibernate.annotations.CacheConcurrencyStrategy; @Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") private long id; 缓存并发策略READ_ONLY 只适用于只读的实体/表NONSTRICT_READ_WRITE 事务提交后会更新缓存,非强一致性,可能读到脏数据,适用于可容忍一定非一致性的数据。READ_WRITE 保证强一致性,缓存实体被更新时会加一个软锁,更新提交后软锁释放。访问加了软锁的缓存实体的并发请求会直接从数据库取数据。TRANSACTIONAL 缓存与数据库数据之间加 XA 分布式事务,保证两者数据一致性。 Hibernate Second-Level Cachehttps://www.baeldung.com/hibernate-second-level-cache 查询(Query)缓存Hibernate 的一、二级缓存都是根据对象 id 来查找时才起作用,如果需要缓存任意查询条件,就需要用到查询缓存。 Hibernate 配置开启 query 缓存 <property name="hibernate.cache.use_query_cache">true</property> 使用 Spring-Data-Jpa 时可在 application.yml 中配置开启 spring: properties: hibernate: cache: use_query_cache: true Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
例2、持久态对象在事务提交前,无论显示执行多少次 save/update 操作,hibernate 都不会发送 sql 语句,只有当事物提交/flush()的时候 hibernate 才会拿当前这个对象与之前保存在session 中的持久化对象进行比较,如果不相同就发送一条 update 的 sql 语句,否则就不会发送 update 语句
@Test public void testExample2() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); // new 对象是临时态 userDO.setName("小明"); session.save(userDO); // save 后变为持久态 userDO.setName("李华"); session.save(userDO); // 不会立即执行 insert 语句 userDO.setAddress("北京市"); session.update(userDO); session.getTransaction().commit(); // 事务结束时发出 insert 和 update 两条语句 第一个 save() 执行完会发出 insert sql,然后后面的 save 和 update 都不会再发出sql,直到最后事务提交时会发出一条 update sql,总共两条sql: Hibernate: insert (address, birthday, has_children, name, id) values (?, ?, ?, ?, ?) Hibernate: update address=?, birthday=?, has_children=?, name=? where 事务提交后持久态对象的修改自动更新到数据库例3、 @Test public void testExample3() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); userDO.setName("姓名改"); session.getTransaction().commit(); // 最后提交事务时对持久态对象的修改会被更新到数据库 之前一段类似下面的代码,忘了写 save() 语句,但发现还是成功更新了user的name,不知道为什么 public void updateUser(req) { UserDO user = jpaRepository.findOne(1L); user.setName(req.getName()); 事务结束时,Hibernate 会自动保存更新了的 DO,在了解 Hibernate 对象状态前不知道为什么,查问题才查到 Hibernate 对象状态相关信息。 https://stackoverflow.com/questions/12859305/hibernate-how-to-disable-automatic-saving-of-dirty-objectshttps://stackoverflow.com/questions/8190926/transactional-saves-without-calling-update-method 游离态对象例4、游离态对象 @Test public void testExample4() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); session.clear(); // 清空 session,user变为游离态 userDO.setName("姓名改"); System.out.println("Entity在当前Session中:" + session.contains(userDO)); session.getTransaction().commit(); // 提交事务时无需更新 调用 session.clear() 方法会将 session 的缓存对象清空,那么 session 中就没有了 user 这个对象,这个时候在提交事务的时候,发现 session 中已经没有该对象了,所以就不会进行任何操作,对象的修改也不会更新到数据库。session.contains(userDO) 返回 false,游离态对象不在 session 中。 持久态对象无法修改ID例5、如果试图修改一个持久化对象的ID值的话,就会抛出异常javax.persistence.PersistenceException: org.hibernate.HibernateException: identifier of an instance of com.masikkk.common.jpa.UserDO was altered from 1 to 22 @Test public void testExample5() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); userDO.setId(22L); // 运行时会抛异常 session.getTransaction().commit(); https://www.cnblogs.com/xiaoluo501395377/p/3380270.htmlhttps://www.baeldung.com/hibernate-session-object-states Hibernate 缓存一级缓存(Session级缓存)一级缓存的生命周期和 Session 一致,也称为 Session 级缓存,或者事务级缓存。一级缓存时默认开启的,不可关闭。 查询:当在同一个 Session 中查询同一个对象时,Hibernate 不会再次与数据库交互,直接从 Session 缓存中取。更新:save/update 更新对象时,Hibernate 不会立即发出 SQL 将这个数据存到数据库,而是将它放在了 Session 的一级缓存中,直到调用 flush() 或提交事务时才会一并存到数据库。 例1、下面两次根据ID查询user,只会执行一次select语句,第二次会从 Session 缓存中获取 @Test public void testSessionCache1() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); System.out.println(session.get(UserDO.class, 1L)); // 有缓存,只执行一次查询 例2、HQL 查询不使用 Session 缓存 @Test public void testSessionCache2() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); // HQL 查询不走缓存,还是会执行sql Query idQuery = session.createQuery("from UserDO where id = :id"); idQuery.setParameter("id", 1L); List<UserDO> userDOList = idQuery.list(); userDOList.forEach(System.out::println); // 再执行一次 HQL 查询还是不走缓存,会再执行一次sql userDOList = idQuery.list(); 例3、虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 @Test public void testSessionCache3() { Session session = HibernateUtils.openSession(); Query nameQuery = session.createQuery("from UserDO where name = :n"); nameQuery.setParameter("n", "李华6"); List<UserDO> userDOList = nameQuery.list(); userDOList.forEach(System.out::println); // 虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 System.out.println(session.get(UserDO.class, 6L)); 二级缓存(应用级缓存)二级缓存是全局性的,可以在多个 Session 中共享数据,二级缓存称为是 SessionFactory 级缓存,或应用级缓存。二级缓存默认没有开启,需要手动配置缓存实现(EhCache等)才可以使用。开启二级缓存后,根据id查询数据时,会先在一级缓存查询,没有再去二级缓存,还没有再去数据库查。在 Spring Data 中可通过 javax.persistence.Cacheable 等注解使用。 注意:一级缓存和二级缓存都是只在根据 ID 查询对象时起作用,并不对全部查询生效。 例1、默认二级缓存是不开启的,两个Session分别查id=1的数据,会执行两次sql @Test public void testSecondLevelCache() { Session session1 = HibernateUtils.openSession(); System.out.println(session1.get(UserDO.class, 1L)); Session session2 = HibernateUtils.openSession(); System.out.println(session2.get(UserDO.class, 1L)); 开启二级缓存1、打开二级缓存开关,测试过程中发现 use_second_level_cache 开关默认是开启的 <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.internal.EhCacheRegionFactory</property> 或在 properties 文件中配置: hibernate.cache.use_second_level_cache=true hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory 或者在 Spring-Boot yml 配置: spring: properties: hibernate: cache: use_second_level_cache: true region: factory_class: org.hibernate.cache.jcache.JCacheRegionFactory provider_configuration_file_resource_path: ehcache.xml 2、二级缓存需要在各个实体上单独开启,注解需要二级缓存的实体 import javax.persistence.Cacheable; import org.hibernate.annotations.CacheConcurrencyStrategy; @Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") private long id; 缓存并发策略READ_ONLY 只适用于只读的实体/表NONSTRICT_READ_WRITE 事务提交后会更新缓存,非强一致性,可能读到脏数据,适用于可容忍一定非一致性的数据。READ_WRITE 保证强一致性,缓存实体被更新时会加一个软锁,更新提交后软锁释放。访问加了软锁的缓存实体的并发请求会直接从数据库取数据。TRANSACTIONAL 缓存与数据库数据之间加 XA 分布式事务,保证两者数据一致性。 Hibernate Second-Level Cachehttps://www.baeldung.com/hibernate-second-level-cache 查询(Query)缓存Hibernate 的一、二级缓存都是根据对象 id 来查找时才起作用,如果需要缓存任意查询条件,就需要用到查询缓存。 Hibernate 配置开启 query 缓存 <property name="hibernate.cache.use_query_cache">true</property> 使用 Spring-Data-Jpa 时可在 application.yml 中配置开启 spring: properties: hibernate: cache: use_query_cache: true Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
@Test public void testExample2() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); // new 对象是临时态 userDO.setName("小明"); session.save(userDO); // save 后变为持久态 userDO.setName("李华"); session.save(userDO); // 不会立即执行 insert 语句 userDO.setAddress("北京市"); session.update(userDO); session.getTransaction().commit(); // 事务结束时发出 insert 和 update 两条语句 第一个 save() 执行完会发出 insert sql,然后后面的 save 和 update 都不会再发出sql,直到最后事务提交时会发出一条 update sql,总共两条sql:
第一个 save() 执行完会发出 insert sql,然后后面的 save 和 update 都不会再发出sql,直到最后事务提交时会发出一条 update sql,总共两条sql:
Hibernate: insert (address, birthday, has_children, name, id) values (?, ?, ?, ?, ?) Hibernate: update address=?, birthday=?, has_children=?, name=? where 事务提交后持久态对象的修改自动更新到数据库例3、 @Test public void testExample3() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); userDO.setName("姓名改"); session.getTransaction().commit(); // 最后提交事务时对持久态对象的修改会被更新到数据库 之前一段类似下面的代码,忘了写 save() 语句,但发现还是成功更新了user的name,不知道为什么 public void updateUser(req) { UserDO user = jpaRepository.findOne(1L); user.setName(req.getName()); 事务结束时,Hibernate 会自动保存更新了的 DO,在了解 Hibernate 对象状态前不知道为什么,查问题才查到 Hibernate 对象状态相关信息。 https://stackoverflow.com/questions/12859305/hibernate-how-to-disable-automatic-saving-of-dirty-objectshttps://stackoverflow.com/questions/8190926/transactional-saves-without-calling-update-method 游离态对象例4、游离态对象 @Test public void testExample4() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); session.clear(); // 清空 session,user变为游离态 userDO.setName("姓名改"); System.out.println("Entity在当前Session中:" + session.contains(userDO)); session.getTransaction().commit(); // 提交事务时无需更新 调用 session.clear() 方法会将 session 的缓存对象清空,那么 session 中就没有了 user 这个对象,这个时候在提交事务的时候,发现 session 中已经没有该对象了,所以就不会进行任何操作,对象的修改也不会更新到数据库。session.contains(userDO) 返回 false,游离态对象不在 session 中。 持久态对象无法修改ID例5、如果试图修改一个持久化对象的ID值的话,就会抛出异常javax.persistence.PersistenceException: org.hibernate.HibernateException: identifier of an instance of com.masikkk.common.jpa.UserDO was altered from 1 to 22 @Test public void testExample5() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); userDO.setId(22L); // 运行时会抛异常 session.getTransaction().commit(); https://www.cnblogs.com/xiaoluo501395377/p/3380270.htmlhttps://www.baeldung.com/hibernate-session-object-states Hibernate 缓存一级缓存(Session级缓存)一级缓存的生命周期和 Session 一致,也称为 Session 级缓存,或者事务级缓存。一级缓存时默认开启的,不可关闭。 查询:当在同一个 Session 中查询同一个对象时,Hibernate 不会再次与数据库交互,直接从 Session 缓存中取。更新:save/update 更新对象时,Hibernate 不会立即发出 SQL 将这个数据存到数据库,而是将它放在了 Session 的一级缓存中,直到调用 flush() 或提交事务时才会一并存到数据库。 例1、下面两次根据ID查询user,只会执行一次select语句,第二次会从 Session 缓存中获取 @Test public void testSessionCache1() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); System.out.println(session.get(UserDO.class, 1L)); // 有缓存,只执行一次查询 例2、HQL 查询不使用 Session 缓存 @Test public void testSessionCache2() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); // HQL 查询不走缓存,还是会执行sql Query idQuery = session.createQuery("from UserDO where id = :id"); idQuery.setParameter("id", 1L); List<UserDO> userDOList = idQuery.list(); userDOList.forEach(System.out::println); // 再执行一次 HQL 查询还是不走缓存,会再执行一次sql userDOList = idQuery.list(); 例3、虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 @Test public void testSessionCache3() { Session session = HibernateUtils.openSession(); Query nameQuery = session.createQuery("from UserDO where name = :n"); nameQuery.setParameter("n", "李华6"); List<UserDO> userDOList = nameQuery.list(); userDOList.forEach(System.out::println); // 虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 System.out.println(session.get(UserDO.class, 6L)); 二级缓存(应用级缓存)二级缓存是全局性的,可以在多个 Session 中共享数据,二级缓存称为是 SessionFactory 级缓存,或应用级缓存。二级缓存默认没有开启,需要手动配置缓存实现(EhCache等)才可以使用。开启二级缓存后,根据id查询数据时,会先在一级缓存查询,没有再去二级缓存,还没有再去数据库查。在 Spring Data 中可通过 javax.persistence.Cacheable 等注解使用。 注意:一级缓存和二级缓存都是只在根据 ID 查询对象时起作用,并不对全部查询生效。 例1、默认二级缓存是不开启的,两个Session分别查id=1的数据,会执行两次sql @Test public void testSecondLevelCache() { Session session1 = HibernateUtils.openSession(); System.out.println(session1.get(UserDO.class, 1L)); Session session2 = HibernateUtils.openSession(); System.out.println(session2.get(UserDO.class, 1L)); 开启二级缓存1、打开二级缓存开关,测试过程中发现 use_second_level_cache 开关默认是开启的 <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.internal.EhCacheRegionFactory</property> 或在 properties 文件中配置: hibernate.cache.use_second_level_cache=true hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory 或者在 Spring-Boot yml 配置: spring: properties: hibernate: cache: use_second_level_cache: true region: factory_class: org.hibernate.cache.jcache.JCacheRegionFactory provider_configuration_file_resource_path: ehcache.xml 2、二级缓存需要在各个实体上单独开启,注解需要二级缓存的实体 import javax.persistence.Cacheable; import org.hibernate.annotations.CacheConcurrencyStrategy; @Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") private long id; 缓存并发策略READ_ONLY 只适用于只读的实体/表NONSTRICT_READ_WRITE 事务提交后会更新缓存,非强一致性,可能读到脏数据,适用于可容忍一定非一致性的数据。READ_WRITE 保证强一致性,缓存实体被更新时会加一个软锁,更新提交后软锁释放。访问加了软锁的缓存实体的并发请求会直接从数据库取数据。TRANSACTIONAL 缓存与数据库数据之间加 XA 分布式事务,保证两者数据一致性。 Hibernate Second-Level Cachehttps://www.baeldung.com/hibernate-second-level-cache 查询(Query)缓存Hibernate 的一、二级缓存都是根据对象 id 来查找时才起作用,如果需要缓存任意查询条件,就需要用到查询缓存。 Hibernate 配置开启 query 缓存 <property name="hibernate.cache.use_query_cache">true</property> 使用 Spring-Data-Jpa 时可在 application.yml 中配置开启 spring: properties: hibernate: cache: use_query_cache: true Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
Hibernate: insert (address, birthday, has_children, name, id) values (?, ?, ?, ?, ?) Hibernate: update address=?, birthday=?, has_children=?, name=? where
事务提交后持久态对象的修改自动更新到数据库例3、 @Test public void testExample3() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); userDO.setName("姓名改"); session.getTransaction().commit(); // 最后提交事务时对持久态对象的修改会被更新到数据库 之前一段类似下面的代码,忘了写 save() 语句,但发现还是成功更新了user的name,不知道为什么 public void updateUser(req) { UserDO user = jpaRepository.findOne(1L); user.setName(req.getName()); 事务结束时,Hibernate 会自动保存更新了的 DO,在了解 Hibernate 对象状态前不知道为什么,查问题才查到 Hibernate 对象状态相关信息。 https://stackoverflow.com/questions/12859305/hibernate-how-to-disable-automatic-saving-of-dirty-objectshttps://stackoverflow.com/questions/8190926/transactional-saves-without-calling-update-method 游离态对象例4、游离态对象 @Test public void testExample4() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); session.clear(); // 清空 session,user变为游离态 userDO.setName("姓名改"); System.out.println("Entity在当前Session中:" + session.contains(userDO)); session.getTransaction().commit(); // 提交事务时无需更新 调用 session.clear() 方法会将 session 的缓存对象清空,那么 session 中就没有了 user 这个对象,这个时候在提交事务的时候,发现 session 中已经没有该对象了,所以就不会进行任何操作,对象的修改也不会更新到数据库。session.contains(userDO) 返回 false,游离态对象不在 session 中。 持久态对象无法修改ID例5、如果试图修改一个持久化对象的ID值的话,就会抛出异常javax.persistence.PersistenceException: org.hibernate.HibernateException: identifier of an instance of com.masikkk.common.jpa.UserDO was altered from 1 to 22 @Test public void testExample5() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); userDO.setId(22L); // 运行时会抛异常 session.getTransaction().commit(); https://www.cnblogs.com/xiaoluo501395377/p/3380270.htmlhttps://www.baeldung.com/hibernate-session-object-states Hibernate 缓存一级缓存(Session级缓存)一级缓存的生命周期和 Session 一致,也称为 Session 级缓存,或者事务级缓存。一级缓存时默认开启的,不可关闭。 查询:当在同一个 Session 中查询同一个对象时,Hibernate 不会再次与数据库交互,直接从 Session 缓存中取。更新:save/update 更新对象时,Hibernate 不会立即发出 SQL 将这个数据存到数据库,而是将它放在了 Session 的一级缓存中,直到调用 flush() 或提交事务时才会一并存到数据库。 例1、下面两次根据ID查询user,只会执行一次select语句,第二次会从 Session 缓存中获取 @Test public void testSessionCache1() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); System.out.println(session.get(UserDO.class, 1L)); // 有缓存,只执行一次查询 例2、HQL 查询不使用 Session 缓存 @Test public void testSessionCache2() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); // HQL 查询不走缓存,还是会执行sql Query idQuery = session.createQuery("from UserDO where id = :id"); idQuery.setParameter("id", 1L); List<UserDO> userDOList = idQuery.list(); userDOList.forEach(System.out::println); // 再执行一次 HQL 查询还是不走缓存,会再执行一次sql userDOList = idQuery.list(); 例3、虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 @Test public void testSessionCache3() { Session session = HibernateUtils.openSession(); Query nameQuery = session.createQuery("from UserDO where name = :n"); nameQuery.setParameter("n", "李华6"); List<UserDO> userDOList = nameQuery.list(); userDOList.forEach(System.out::println); // 虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 System.out.println(session.get(UserDO.class, 6L)); 二级缓存(应用级缓存)二级缓存是全局性的,可以在多个 Session 中共享数据,二级缓存称为是 SessionFactory 级缓存,或应用级缓存。二级缓存默认没有开启,需要手动配置缓存实现(EhCache等)才可以使用。开启二级缓存后,根据id查询数据时,会先在一级缓存查询,没有再去二级缓存,还没有再去数据库查。在 Spring Data 中可通过 javax.persistence.Cacheable 等注解使用。 注意:一级缓存和二级缓存都是只在根据 ID 查询对象时起作用,并不对全部查询生效。 例1、默认二级缓存是不开启的,两个Session分别查id=1的数据,会执行两次sql @Test public void testSecondLevelCache() { Session session1 = HibernateUtils.openSession(); System.out.println(session1.get(UserDO.class, 1L)); Session session2 = HibernateUtils.openSession(); System.out.println(session2.get(UserDO.class, 1L)); 开启二级缓存1、打开二级缓存开关,测试过程中发现 use_second_level_cache 开关默认是开启的 <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.internal.EhCacheRegionFactory</property> 或在 properties 文件中配置: hibernate.cache.use_second_level_cache=true hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory 或者在 Spring-Boot yml 配置: spring: properties: hibernate: cache: use_second_level_cache: true region: factory_class: org.hibernate.cache.jcache.JCacheRegionFactory provider_configuration_file_resource_path: ehcache.xml 2、二级缓存需要在各个实体上单独开启,注解需要二级缓存的实体 import javax.persistence.Cacheable; import org.hibernate.annotations.CacheConcurrencyStrategy; @Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") private long id; 缓存并发策略READ_ONLY 只适用于只读的实体/表NONSTRICT_READ_WRITE 事务提交后会更新缓存,非强一致性,可能读到脏数据,适用于可容忍一定非一致性的数据。READ_WRITE 保证强一致性,缓存实体被更新时会加一个软锁,更新提交后软锁释放。访问加了软锁的缓存实体的并发请求会直接从数据库取数据。TRANSACTIONAL 缓存与数据库数据之间加 XA 分布式事务,保证两者数据一致性。 Hibernate Second-Level Cachehttps://www.baeldung.com/hibernate-second-level-cache 查询(Query)缓存Hibernate 的一、二级缓存都是根据对象 id 来查找时才起作用,如果需要缓存任意查询条件,就需要用到查询缓存。 Hibernate 配置开启 query 缓存 <property name="hibernate.cache.use_query_cache">true</property> 使用 Spring-Data-Jpa 时可在 application.yml 中配置开启 spring: properties: hibernate: cache: use_query_cache: true Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
例3、
@Test public void testExample3() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); userDO.setName("姓名改"); session.getTransaction().commit(); // 最后提交事务时对持久态对象的修改会被更新到数据库 之前一段类似下面的代码,忘了写 save() 语句,但发现还是成功更新了user的name,不知道为什么 public void updateUser(req) { UserDO user = jpaRepository.findOne(1L); user.setName(req.getName()); 事务结束时,Hibernate 会自动保存更新了的 DO,在了解 Hibernate 对象状态前不知道为什么,查问题才查到 Hibernate 对象状态相关信息。 https://stackoverflow.com/questions/12859305/hibernate-how-to-disable-automatic-saving-of-dirty-objectshttps://stackoverflow.com/questions/8190926/transactional-saves-without-calling-update-method 游离态对象例4、游离态对象 @Test public void testExample4() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); session.clear(); // 清空 session,user变为游离态 userDO.setName("姓名改"); System.out.println("Entity在当前Session中:" + session.contains(userDO)); session.getTransaction().commit(); // 提交事务时无需更新 调用 session.clear() 方法会将 session 的缓存对象清空,那么 session 中就没有了 user 这个对象,这个时候在提交事务的时候,发现 session 中已经没有该对象了,所以就不会进行任何操作,对象的修改也不会更新到数据库。session.contains(userDO) 返回 false,游离态对象不在 session 中。 持久态对象无法修改ID例5、如果试图修改一个持久化对象的ID值的话,就会抛出异常javax.persistence.PersistenceException: org.hibernate.HibernateException: identifier of an instance of com.masikkk.common.jpa.UserDO was altered from 1 to 22 @Test public void testExample5() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); userDO.setId(22L); // 运行时会抛异常 session.getTransaction().commit(); https://www.cnblogs.com/xiaoluo501395377/p/3380270.htmlhttps://www.baeldung.com/hibernate-session-object-states Hibernate 缓存一级缓存(Session级缓存)一级缓存的生命周期和 Session 一致,也称为 Session 级缓存,或者事务级缓存。一级缓存时默认开启的,不可关闭。 查询:当在同一个 Session 中查询同一个对象时,Hibernate 不会再次与数据库交互,直接从 Session 缓存中取。更新:save/update 更新对象时,Hibernate 不会立即发出 SQL 将这个数据存到数据库,而是将它放在了 Session 的一级缓存中,直到调用 flush() 或提交事务时才会一并存到数据库。 例1、下面两次根据ID查询user,只会执行一次select语句,第二次会从 Session 缓存中获取 @Test public void testSessionCache1() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); System.out.println(session.get(UserDO.class, 1L)); // 有缓存,只执行一次查询 例2、HQL 查询不使用 Session 缓存 @Test public void testSessionCache2() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); // HQL 查询不走缓存,还是会执行sql Query idQuery = session.createQuery("from UserDO where id = :id"); idQuery.setParameter("id", 1L); List<UserDO> userDOList = idQuery.list(); userDOList.forEach(System.out::println); // 再执行一次 HQL 查询还是不走缓存,会再执行一次sql userDOList = idQuery.list(); 例3、虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 @Test public void testSessionCache3() { Session session = HibernateUtils.openSession(); Query nameQuery = session.createQuery("from UserDO where name = :n"); nameQuery.setParameter("n", "李华6"); List<UserDO> userDOList = nameQuery.list(); userDOList.forEach(System.out::println); // 虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 System.out.println(session.get(UserDO.class, 6L)); 二级缓存(应用级缓存)二级缓存是全局性的,可以在多个 Session 中共享数据,二级缓存称为是 SessionFactory 级缓存,或应用级缓存。二级缓存默认没有开启,需要手动配置缓存实现(EhCache等)才可以使用。开启二级缓存后,根据id查询数据时,会先在一级缓存查询,没有再去二级缓存,还没有再去数据库查。在 Spring Data 中可通过 javax.persistence.Cacheable 等注解使用。 注意:一级缓存和二级缓存都是只在根据 ID 查询对象时起作用,并不对全部查询生效。 例1、默认二级缓存是不开启的,两个Session分别查id=1的数据,会执行两次sql @Test public void testSecondLevelCache() { Session session1 = HibernateUtils.openSession(); System.out.println(session1.get(UserDO.class, 1L)); Session session2 = HibernateUtils.openSession(); System.out.println(session2.get(UserDO.class, 1L)); 开启二级缓存1、打开二级缓存开关,测试过程中发现 use_second_level_cache 开关默认是开启的 <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.internal.EhCacheRegionFactory</property> 或在 properties 文件中配置: hibernate.cache.use_second_level_cache=true hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory 或者在 Spring-Boot yml 配置: spring: properties: hibernate: cache: use_second_level_cache: true region: factory_class: org.hibernate.cache.jcache.JCacheRegionFactory provider_configuration_file_resource_path: ehcache.xml 2、二级缓存需要在各个实体上单独开启,注解需要二级缓存的实体 import javax.persistence.Cacheable; import org.hibernate.annotations.CacheConcurrencyStrategy; @Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") private long id; 缓存并发策略READ_ONLY 只适用于只读的实体/表NONSTRICT_READ_WRITE 事务提交后会更新缓存,非强一致性,可能读到脏数据,适用于可容忍一定非一致性的数据。READ_WRITE 保证强一致性,缓存实体被更新时会加一个软锁,更新提交后软锁释放。访问加了软锁的缓存实体的并发请求会直接从数据库取数据。TRANSACTIONAL 缓存与数据库数据之间加 XA 分布式事务,保证两者数据一致性。 Hibernate Second-Level Cachehttps://www.baeldung.com/hibernate-second-level-cache 查询(Query)缓存Hibernate 的一、二级缓存都是根据对象 id 来查找时才起作用,如果需要缓存任意查询条件,就需要用到查询缓存。 Hibernate 配置开启 query 缓存 <property name="hibernate.cache.use_query_cache">true</property> 使用 Spring-Data-Jpa 时可在 application.yml 中配置开启 spring: properties: hibernate: cache: use_query_cache: true Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
@Test public void testExample3() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); userDO.setName("姓名改"); session.getTransaction().commit(); // 最后提交事务时对持久态对象的修改会被更新到数据库 之前一段类似下面的代码,忘了写 save() 语句,但发现还是成功更新了user的name,不知道为什么
之前一段类似下面的代码,忘了写 save() 语句,但发现还是成功更新了user的name,不知道为什么
public void updateUser(req) { UserDO user = jpaRepository.findOne(1L); user.setName(req.getName()); 事务结束时,Hibernate 会自动保存更新了的 DO,在了解 Hibernate 对象状态前不知道为什么,查问题才查到 Hibernate 对象状态相关信息。 https://stackoverflow.com/questions/12859305/hibernate-how-to-disable-automatic-saving-of-dirty-objectshttps://stackoverflow.com/questions/8190926/transactional-saves-without-calling-update-method 游离态对象例4、游离态对象 @Test public void testExample4() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); session.clear(); // 清空 session,user变为游离态 userDO.setName("姓名改"); System.out.println("Entity在当前Session中:" + session.contains(userDO)); session.getTransaction().commit(); // 提交事务时无需更新 调用 session.clear() 方法会将 session 的缓存对象清空,那么 session 中就没有了 user 这个对象,这个时候在提交事务的时候,发现 session 中已经没有该对象了,所以就不会进行任何操作,对象的修改也不会更新到数据库。session.contains(userDO) 返回 false,游离态对象不在 session 中。 持久态对象无法修改ID例5、如果试图修改一个持久化对象的ID值的话,就会抛出异常javax.persistence.PersistenceException: org.hibernate.HibernateException: identifier of an instance of com.masikkk.common.jpa.UserDO was altered from 1 to 22 @Test public void testExample5() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); userDO.setId(22L); // 运行时会抛异常 session.getTransaction().commit(); https://www.cnblogs.com/xiaoluo501395377/p/3380270.htmlhttps://www.baeldung.com/hibernate-session-object-states Hibernate 缓存一级缓存(Session级缓存)一级缓存的生命周期和 Session 一致,也称为 Session 级缓存,或者事务级缓存。一级缓存时默认开启的,不可关闭。 查询:当在同一个 Session 中查询同一个对象时,Hibernate 不会再次与数据库交互,直接从 Session 缓存中取。更新:save/update 更新对象时,Hibernate 不会立即发出 SQL 将这个数据存到数据库,而是将它放在了 Session 的一级缓存中,直到调用 flush() 或提交事务时才会一并存到数据库。 例1、下面两次根据ID查询user,只会执行一次select语句,第二次会从 Session 缓存中获取 @Test public void testSessionCache1() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); System.out.println(session.get(UserDO.class, 1L)); // 有缓存,只执行一次查询 例2、HQL 查询不使用 Session 缓存 @Test public void testSessionCache2() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); // HQL 查询不走缓存,还是会执行sql Query idQuery = session.createQuery("from UserDO where id = :id"); idQuery.setParameter("id", 1L); List<UserDO> userDOList = idQuery.list(); userDOList.forEach(System.out::println); // 再执行一次 HQL 查询还是不走缓存,会再执行一次sql userDOList = idQuery.list(); 例3、虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 @Test public void testSessionCache3() { Session session = HibernateUtils.openSession(); Query nameQuery = session.createQuery("from UserDO where name = :n"); nameQuery.setParameter("n", "李华6"); List<UserDO> userDOList = nameQuery.list(); userDOList.forEach(System.out::println); // 虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 System.out.println(session.get(UserDO.class, 6L)); 二级缓存(应用级缓存)二级缓存是全局性的,可以在多个 Session 中共享数据,二级缓存称为是 SessionFactory 级缓存,或应用级缓存。二级缓存默认没有开启,需要手动配置缓存实现(EhCache等)才可以使用。开启二级缓存后,根据id查询数据时,会先在一级缓存查询,没有再去二级缓存,还没有再去数据库查。在 Spring Data 中可通过 javax.persistence.Cacheable 等注解使用。 注意:一级缓存和二级缓存都是只在根据 ID 查询对象时起作用,并不对全部查询生效。 例1、默认二级缓存是不开启的,两个Session分别查id=1的数据,会执行两次sql @Test public void testSecondLevelCache() { Session session1 = HibernateUtils.openSession(); System.out.println(session1.get(UserDO.class, 1L)); Session session2 = HibernateUtils.openSession(); System.out.println(session2.get(UserDO.class, 1L)); 开启二级缓存1、打开二级缓存开关,测试过程中发现 use_second_level_cache 开关默认是开启的 <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.internal.EhCacheRegionFactory</property> 或在 properties 文件中配置: hibernate.cache.use_second_level_cache=true hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory 或者在 Spring-Boot yml 配置: spring: properties: hibernate: cache: use_second_level_cache: true region: factory_class: org.hibernate.cache.jcache.JCacheRegionFactory provider_configuration_file_resource_path: ehcache.xml 2、二级缓存需要在各个实体上单独开启,注解需要二级缓存的实体 import javax.persistence.Cacheable; import org.hibernate.annotations.CacheConcurrencyStrategy; @Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") private long id; 缓存并发策略READ_ONLY 只适用于只读的实体/表NONSTRICT_READ_WRITE 事务提交后会更新缓存,非强一致性,可能读到脏数据,适用于可容忍一定非一致性的数据。READ_WRITE 保证强一致性,缓存实体被更新时会加一个软锁,更新提交后软锁释放。访问加了软锁的缓存实体的并发请求会直接从数据库取数据。TRANSACTIONAL 缓存与数据库数据之间加 XA 分布式事务,保证两者数据一致性。 Hibernate Second-Level Cachehttps://www.baeldung.com/hibernate-second-level-cache 查询(Query)缓存Hibernate 的一、二级缓存都是根据对象 id 来查找时才起作用,如果需要缓存任意查询条件,就需要用到查询缓存。 Hibernate 配置开启 query 缓存 <property name="hibernate.cache.use_query_cache">true</property> 使用 Spring-Data-Jpa 时可在 application.yml 中配置开启 spring: properties: hibernate: cache: use_query_cache: true Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
public void updateUser(req) { UserDO user = jpaRepository.findOne(1L); user.setName(req.getName()); 事务结束时,Hibernate 会自动保存更新了的 DO,在了解 Hibernate 对象状态前不知道为什么,查问题才查到 Hibernate 对象状态相关信息。
事务结束时,Hibernate 会自动保存更新了的 DO,在了解 Hibernate 对象状态前不知道为什么,查问题才查到 Hibernate 对象状态相关信息。
https://stackoverflow.com/questions/12859305/hibernate-how-to-disable-automatic-saving-of-dirty-objectshttps://stackoverflow.com/questions/8190926/transactional-saves-without-calling-update-method 游离态对象例4、游离态对象 @Test public void testExample4() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); session.clear(); // 清空 session,user变为游离态 userDO.setName("姓名改"); System.out.println("Entity在当前Session中:" + session.contains(userDO)); session.getTransaction().commit(); // 提交事务时无需更新 调用 session.clear() 方法会将 session 的缓存对象清空,那么 session 中就没有了 user 这个对象,这个时候在提交事务的时候,发现 session 中已经没有该对象了,所以就不会进行任何操作,对象的修改也不会更新到数据库。session.contains(userDO) 返回 false,游离态对象不在 session 中。 持久态对象无法修改ID例5、如果试图修改一个持久化对象的ID值的话,就会抛出异常javax.persistence.PersistenceException: org.hibernate.HibernateException: identifier of an instance of com.masikkk.common.jpa.UserDO was altered from 1 to 22 @Test public void testExample5() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); userDO.setId(22L); // 运行时会抛异常 session.getTransaction().commit(); https://www.cnblogs.com/xiaoluo501395377/p/3380270.htmlhttps://www.baeldung.com/hibernate-session-object-states Hibernate 缓存一级缓存(Session级缓存)一级缓存的生命周期和 Session 一致,也称为 Session 级缓存,或者事务级缓存。一级缓存时默认开启的,不可关闭。 查询:当在同一个 Session 中查询同一个对象时,Hibernate 不会再次与数据库交互,直接从 Session 缓存中取。更新:save/update 更新对象时,Hibernate 不会立即发出 SQL 将这个数据存到数据库,而是将它放在了 Session 的一级缓存中,直到调用 flush() 或提交事务时才会一并存到数据库。 例1、下面两次根据ID查询user,只会执行一次select语句,第二次会从 Session 缓存中获取 @Test public void testSessionCache1() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); System.out.println(session.get(UserDO.class, 1L)); // 有缓存,只执行一次查询 例2、HQL 查询不使用 Session 缓存 @Test public void testSessionCache2() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); // HQL 查询不走缓存,还是会执行sql Query idQuery = session.createQuery("from UserDO where id = :id"); idQuery.setParameter("id", 1L); List<UserDO> userDOList = idQuery.list(); userDOList.forEach(System.out::println); // 再执行一次 HQL 查询还是不走缓存,会再执行一次sql userDOList = idQuery.list(); 例3、虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 @Test public void testSessionCache3() { Session session = HibernateUtils.openSession(); Query nameQuery = session.createQuery("from UserDO where name = :n"); nameQuery.setParameter("n", "李华6"); List<UserDO> userDOList = nameQuery.list(); userDOList.forEach(System.out::println); // 虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 System.out.println(session.get(UserDO.class, 6L)); 二级缓存(应用级缓存)二级缓存是全局性的,可以在多个 Session 中共享数据,二级缓存称为是 SessionFactory 级缓存,或应用级缓存。二级缓存默认没有开启,需要手动配置缓存实现(EhCache等)才可以使用。开启二级缓存后,根据id查询数据时,会先在一级缓存查询,没有再去二级缓存,还没有再去数据库查。在 Spring Data 中可通过 javax.persistence.Cacheable 等注解使用。 注意:一级缓存和二级缓存都是只在根据 ID 查询对象时起作用,并不对全部查询生效。 例1、默认二级缓存是不开启的,两个Session分别查id=1的数据,会执行两次sql @Test public void testSecondLevelCache() { Session session1 = HibernateUtils.openSession(); System.out.println(session1.get(UserDO.class, 1L)); Session session2 = HibernateUtils.openSession(); System.out.println(session2.get(UserDO.class, 1L)); 开启二级缓存1、打开二级缓存开关,测试过程中发现 use_second_level_cache 开关默认是开启的 <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.internal.EhCacheRegionFactory</property> 或在 properties 文件中配置: hibernate.cache.use_second_level_cache=true hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory 或者在 Spring-Boot yml 配置: spring: properties: hibernate: cache: use_second_level_cache: true region: factory_class: org.hibernate.cache.jcache.JCacheRegionFactory provider_configuration_file_resource_path: ehcache.xml 2、二级缓存需要在各个实体上单独开启,注解需要二级缓存的实体 import javax.persistence.Cacheable; import org.hibernate.annotations.CacheConcurrencyStrategy; @Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") private long id; 缓存并发策略READ_ONLY 只适用于只读的实体/表NONSTRICT_READ_WRITE 事务提交后会更新缓存,非强一致性,可能读到脏数据,适用于可容忍一定非一致性的数据。READ_WRITE 保证强一致性,缓存实体被更新时会加一个软锁,更新提交后软锁释放。访问加了软锁的缓存实体的并发请求会直接从数据库取数据。TRANSACTIONAL 缓存与数据库数据之间加 XA 分布式事务,保证两者数据一致性。 Hibernate Second-Level Cachehttps://www.baeldung.com/hibernate-second-level-cache 查询(Query)缓存Hibernate 的一、二级缓存都是根据对象 id 来查找时才起作用,如果需要缓存任意查询条件,就需要用到查询缓存。 Hibernate 配置开启 query 缓存 <property name="hibernate.cache.use_query_cache">true</property> 使用 Spring-Data-Jpa 时可在 application.yml 中配置开启 spring: properties: hibernate: cache: use_query_cache: true Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
https://stackoverflow.com/questions/12859305/hibernate-how-to-disable-automatic-saving-of-dirty-objectshttps://stackoverflow.com/questions/8190926/transactional-saves-without-calling-update-method
例4、游离态对象
@Test public void testExample4() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); session.clear(); // 清空 session,user变为游离态 userDO.setName("姓名改"); System.out.println("Entity在当前Session中:" + session.contains(userDO)); session.getTransaction().commit(); // 提交事务时无需更新 调用 session.clear() 方法会将 session 的缓存对象清空,那么 session 中就没有了 user 这个对象,这个时候在提交事务的时候,发现 session 中已经没有该对象了,所以就不会进行任何操作,对象的修改也不会更新到数据库。session.contains(userDO) 返回 false,游离态对象不在 session 中。 持久态对象无法修改ID例5、如果试图修改一个持久化对象的ID值的话,就会抛出异常javax.persistence.PersistenceException: org.hibernate.HibernateException: identifier of an instance of com.masikkk.common.jpa.UserDO was altered from 1 to 22 @Test public void testExample5() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); userDO.setId(22L); // 运行时会抛异常 session.getTransaction().commit(); https://www.cnblogs.com/xiaoluo501395377/p/3380270.htmlhttps://www.baeldung.com/hibernate-session-object-states Hibernate 缓存一级缓存(Session级缓存)一级缓存的生命周期和 Session 一致,也称为 Session 级缓存,或者事务级缓存。一级缓存时默认开启的,不可关闭。 查询:当在同一个 Session 中查询同一个对象时,Hibernate 不会再次与数据库交互,直接从 Session 缓存中取。更新:save/update 更新对象时,Hibernate 不会立即发出 SQL 将这个数据存到数据库,而是将它放在了 Session 的一级缓存中,直到调用 flush() 或提交事务时才会一并存到数据库。 例1、下面两次根据ID查询user,只会执行一次select语句,第二次会从 Session 缓存中获取 @Test public void testSessionCache1() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); System.out.println(session.get(UserDO.class, 1L)); // 有缓存,只执行一次查询 例2、HQL 查询不使用 Session 缓存 @Test public void testSessionCache2() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); // HQL 查询不走缓存,还是会执行sql Query idQuery = session.createQuery("from UserDO where id = :id"); idQuery.setParameter("id", 1L); List<UserDO> userDOList = idQuery.list(); userDOList.forEach(System.out::println); // 再执行一次 HQL 查询还是不走缓存,会再执行一次sql userDOList = idQuery.list(); 例3、虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 @Test public void testSessionCache3() { Session session = HibernateUtils.openSession(); Query nameQuery = session.createQuery("from UserDO where name = :n"); nameQuery.setParameter("n", "李华6"); List<UserDO> userDOList = nameQuery.list(); userDOList.forEach(System.out::println); // 虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 System.out.println(session.get(UserDO.class, 6L)); 二级缓存(应用级缓存)二级缓存是全局性的,可以在多个 Session 中共享数据,二级缓存称为是 SessionFactory 级缓存,或应用级缓存。二级缓存默认没有开启,需要手动配置缓存实现(EhCache等)才可以使用。开启二级缓存后,根据id查询数据时,会先在一级缓存查询,没有再去二级缓存,还没有再去数据库查。在 Spring Data 中可通过 javax.persistence.Cacheable 等注解使用。 注意:一级缓存和二级缓存都是只在根据 ID 查询对象时起作用,并不对全部查询生效。 例1、默认二级缓存是不开启的,两个Session分别查id=1的数据,会执行两次sql @Test public void testSecondLevelCache() { Session session1 = HibernateUtils.openSession(); System.out.println(session1.get(UserDO.class, 1L)); Session session2 = HibernateUtils.openSession(); System.out.println(session2.get(UserDO.class, 1L)); 开启二级缓存1、打开二级缓存开关,测试过程中发现 use_second_level_cache 开关默认是开启的 <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.internal.EhCacheRegionFactory</property> 或在 properties 文件中配置: hibernate.cache.use_second_level_cache=true hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory 或者在 Spring-Boot yml 配置: spring: properties: hibernate: cache: use_second_level_cache: true region: factory_class: org.hibernate.cache.jcache.JCacheRegionFactory provider_configuration_file_resource_path: ehcache.xml 2、二级缓存需要在各个实体上单独开启,注解需要二级缓存的实体 import javax.persistence.Cacheable; import org.hibernate.annotations.CacheConcurrencyStrategy; @Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") private long id; 缓存并发策略READ_ONLY 只适用于只读的实体/表NONSTRICT_READ_WRITE 事务提交后会更新缓存,非强一致性,可能读到脏数据,适用于可容忍一定非一致性的数据。READ_WRITE 保证强一致性,缓存实体被更新时会加一个软锁,更新提交后软锁释放。访问加了软锁的缓存实体的并发请求会直接从数据库取数据。TRANSACTIONAL 缓存与数据库数据之间加 XA 分布式事务,保证两者数据一致性。 Hibernate Second-Level Cachehttps://www.baeldung.com/hibernate-second-level-cache 查询(Query)缓存Hibernate 的一、二级缓存都是根据对象 id 来查找时才起作用,如果需要缓存任意查询条件,就需要用到查询缓存。 Hibernate 配置开启 query 缓存 <property name="hibernate.cache.use_query_cache">true</property> 使用 Spring-Data-Jpa 时可在 application.yml 中配置开启 spring: properties: hibernate: cache: use_query_cache: true Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
@Test public void testExample4() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); session.clear(); // 清空 session,user变为游离态 userDO.setName("姓名改"); System.out.println("Entity在当前Session中:" + session.contains(userDO)); session.getTransaction().commit(); // 提交事务时无需更新 调用 session.clear() 方法会将 session 的缓存对象清空,那么 session 中就没有了 user 这个对象,这个时候在提交事务的时候,发现 session 中已经没有该对象了,所以就不会进行任何操作,对象的修改也不会更新到数据库。session.contains(userDO) 返回 false,游离态对象不在 session 中。
调用 session.clear() 方法会将 session 的缓存对象清空,那么 session 中就没有了 user 这个对象,这个时候在提交事务的时候,发现 session 中已经没有该对象了,所以就不会进行任何操作,对象的修改也不会更新到数据库。session.contains(userDO) 返回 false,游离态对象不在 session 中。
持久态对象无法修改ID例5、如果试图修改一个持久化对象的ID值的话,就会抛出异常javax.persistence.PersistenceException: org.hibernate.HibernateException: identifier of an instance of com.masikkk.common.jpa.UserDO was altered from 1 to 22 @Test public void testExample5() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); userDO.setId(22L); // 运行时会抛异常 session.getTransaction().commit(); https://www.cnblogs.com/xiaoluo501395377/p/3380270.htmlhttps://www.baeldung.com/hibernate-session-object-states Hibernate 缓存一级缓存(Session级缓存)一级缓存的生命周期和 Session 一致,也称为 Session 级缓存,或者事务级缓存。一级缓存时默认开启的,不可关闭。 查询:当在同一个 Session 中查询同一个对象时,Hibernate 不会再次与数据库交互,直接从 Session 缓存中取。更新:save/update 更新对象时,Hibernate 不会立即发出 SQL 将这个数据存到数据库,而是将它放在了 Session 的一级缓存中,直到调用 flush() 或提交事务时才会一并存到数据库。 例1、下面两次根据ID查询user,只会执行一次select语句,第二次会从 Session 缓存中获取 @Test public void testSessionCache1() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); System.out.println(session.get(UserDO.class, 1L)); // 有缓存,只执行一次查询 例2、HQL 查询不使用 Session 缓存 @Test public void testSessionCache2() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); // HQL 查询不走缓存,还是会执行sql Query idQuery = session.createQuery("from UserDO where id = :id"); idQuery.setParameter("id", 1L); List<UserDO> userDOList = idQuery.list(); userDOList.forEach(System.out::println); // 再执行一次 HQL 查询还是不走缓存,会再执行一次sql userDOList = idQuery.list(); 例3、虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 @Test public void testSessionCache3() { Session session = HibernateUtils.openSession(); Query nameQuery = session.createQuery("from UserDO where name = :n"); nameQuery.setParameter("n", "李华6"); List<UserDO> userDOList = nameQuery.list(); userDOList.forEach(System.out::println); // 虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 System.out.println(session.get(UserDO.class, 6L)); 二级缓存(应用级缓存)二级缓存是全局性的,可以在多个 Session 中共享数据,二级缓存称为是 SessionFactory 级缓存,或应用级缓存。二级缓存默认没有开启,需要手动配置缓存实现(EhCache等)才可以使用。开启二级缓存后,根据id查询数据时,会先在一级缓存查询,没有再去二级缓存,还没有再去数据库查。在 Spring Data 中可通过 javax.persistence.Cacheable 等注解使用。 注意:一级缓存和二级缓存都是只在根据 ID 查询对象时起作用,并不对全部查询生效。 例1、默认二级缓存是不开启的,两个Session分别查id=1的数据,会执行两次sql @Test public void testSecondLevelCache() { Session session1 = HibernateUtils.openSession(); System.out.println(session1.get(UserDO.class, 1L)); Session session2 = HibernateUtils.openSession(); System.out.println(session2.get(UserDO.class, 1L)); 开启二级缓存1、打开二级缓存开关,测试过程中发现 use_second_level_cache 开关默认是开启的 <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.internal.EhCacheRegionFactory</property> 或在 properties 文件中配置: hibernate.cache.use_second_level_cache=true hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory 或者在 Spring-Boot yml 配置: spring: properties: hibernate: cache: use_second_level_cache: true region: factory_class: org.hibernate.cache.jcache.JCacheRegionFactory provider_configuration_file_resource_path: ehcache.xml 2、二级缓存需要在各个实体上单独开启,注解需要二级缓存的实体 import javax.persistence.Cacheable; import org.hibernate.annotations.CacheConcurrencyStrategy; @Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") private long id; 缓存并发策略READ_ONLY 只适用于只读的实体/表NONSTRICT_READ_WRITE 事务提交后会更新缓存,非强一致性,可能读到脏数据,适用于可容忍一定非一致性的数据。READ_WRITE 保证强一致性,缓存实体被更新时会加一个软锁,更新提交后软锁释放。访问加了软锁的缓存实体的并发请求会直接从数据库取数据。TRANSACTIONAL 缓存与数据库数据之间加 XA 分布式事务,保证两者数据一致性。 Hibernate Second-Level Cachehttps://www.baeldung.com/hibernate-second-level-cache 查询(Query)缓存Hibernate 的一、二级缓存都是根据对象 id 来查找时才起作用,如果需要缓存任意查询条件,就需要用到查询缓存。 Hibernate 配置开启 query 缓存 <property name="hibernate.cache.use_query_cache">true</property> 使用 Spring-Data-Jpa 时可在 application.yml 中配置开启 spring: properties: hibernate: cache: use_query_cache: true Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
例5、如果试图修改一个持久化对象的ID值的话,就会抛出异常javax.persistence.PersistenceException: org.hibernate.HibernateException: identifier of an instance of com.masikkk.common.jpa.UserDO was altered from 1 to 22
@Test public void testExample5() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); userDO.setId(22L); // 运行时会抛异常 session.getTransaction().commit(); https://www.cnblogs.com/xiaoluo501395377/p/3380270.htmlhttps://www.baeldung.com/hibernate-session-object-states Hibernate 缓存一级缓存(Session级缓存)一级缓存的生命周期和 Session 一致,也称为 Session 级缓存,或者事务级缓存。一级缓存时默认开启的,不可关闭。 查询:当在同一个 Session 中查询同一个对象时,Hibernate 不会再次与数据库交互,直接从 Session 缓存中取。更新:save/update 更新对象时,Hibernate 不会立即发出 SQL 将这个数据存到数据库,而是将它放在了 Session 的一级缓存中,直到调用 flush() 或提交事务时才会一并存到数据库。 例1、下面两次根据ID查询user,只会执行一次select语句,第二次会从 Session 缓存中获取 @Test public void testSessionCache1() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); System.out.println(session.get(UserDO.class, 1L)); // 有缓存,只执行一次查询 例2、HQL 查询不使用 Session 缓存 @Test public void testSessionCache2() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); // HQL 查询不走缓存,还是会执行sql Query idQuery = session.createQuery("from UserDO where id = :id"); idQuery.setParameter("id", 1L); List<UserDO> userDOList = idQuery.list(); userDOList.forEach(System.out::println); // 再执行一次 HQL 查询还是不走缓存,会再执行一次sql userDOList = idQuery.list(); 例3、虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 @Test public void testSessionCache3() { Session session = HibernateUtils.openSession(); Query nameQuery = session.createQuery("from UserDO where name = :n"); nameQuery.setParameter("n", "李华6"); List<UserDO> userDOList = nameQuery.list(); userDOList.forEach(System.out::println); // 虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 System.out.println(session.get(UserDO.class, 6L)); 二级缓存(应用级缓存)二级缓存是全局性的,可以在多个 Session 中共享数据,二级缓存称为是 SessionFactory 级缓存,或应用级缓存。二级缓存默认没有开启,需要手动配置缓存实现(EhCache等)才可以使用。开启二级缓存后,根据id查询数据时,会先在一级缓存查询,没有再去二级缓存,还没有再去数据库查。在 Spring Data 中可通过 javax.persistence.Cacheable 等注解使用。 注意:一级缓存和二级缓存都是只在根据 ID 查询对象时起作用,并不对全部查询生效。 例1、默认二级缓存是不开启的,两个Session分别查id=1的数据,会执行两次sql @Test public void testSecondLevelCache() { Session session1 = HibernateUtils.openSession(); System.out.println(session1.get(UserDO.class, 1L)); Session session2 = HibernateUtils.openSession(); System.out.println(session2.get(UserDO.class, 1L)); 开启二级缓存1、打开二级缓存开关,测试过程中发现 use_second_level_cache 开关默认是开启的 <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.internal.EhCacheRegionFactory</property> 或在 properties 文件中配置: hibernate.cache.use_second_level_cache=true hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory 或者在 Spring-Boot yml 配置: spring: properties: hibernate: cache: use_second_level_cache: true region: factory_class: org.hibernate.cache.jcache.JCacheRegionFactory provider_configuration_file_resource_path: ehcache.xml 2、二级缓存需要在各个实体上单独开启,注解需要二级缓存的实体 import javax.persistence.Cacheable; import org.hibernate.annotations.CacheConcurrencyStrategy; @Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") private long id; 缓存并发策略READ_ONLY 只适用于只读的实体/表NONSTRICT_READ_WRITE 事务提交后会更新缓存,非强一致性,可能读到脏数据,适用于可容忍一定非一致性的数据。READ_WRITE 保证强一致性,缓存实体被更新时会加一个软锁,更新提交后软锁释放。访问加了软锁的缓存实体的并发请求会直接从数据库取数据。TRANSACTIONAL 缓存与数据库数据之间加 XA 分布式事务,保证两者数据一致性。 Hibernate Second-Level Cachehttps://www.baeldung.com/hibernate-second-level-cache 查询(Query)缓存Hibernate 的一、二级缓存都是根据对象 id 来查找时才起作用,如果需要缓存任意查询条件,就需要用到查询缓存。 Hibernate 配置开启 query 缓存 <property name="hibernate.cache.use_query_cache">true</property> 使用 Spring-Data-Jpa 时可在 application.yml 中配置开启 spring: properties: hibernate: cache: use_query_cache: true Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
@Test public void testExample5() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = session.get(UserDO.class, 1L); // 查询出来的是持久态对象 System.out.println(userDO); userDO.setId(22L); // 运行时会抛异常 session.getTransaction().commit();
https://www.cnblogs.com/xiaoluo501395377/p/3380270.htmlhttps://www.baeldung.com/hibernate-session-object-states Hibernate 缓存一级缓存(Session级缓存)一级缓存的生命周期和 Session 一致,也称为 Session 级缓存,或者事务级缓存。一级缓存时默认开启的,不可关闭。 查询:当在同一个 Session 中查询同一个对象时,Hibernate 不会再次与数据库交互,直接从 Session 缓存中取。更新:save/update 更新对象时,Hibernate 不会立即发出 SQL 将这个数据存到数据库,而是将它放在了 Session 的一级缓存中,直到调用 flush() 或提交事务时才会一并存到数据库。 例1、下面两次根据ID查询user,只会执行一次select语句,第二次会从 Session 缓存中获取 @Test public void testSessionCache1() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); System.out.println(session.get(UserDO.class, 1L)); // 有缓存,只执行一次查询 例2、HQL 查询不使用 Session 缓存 @Test public void testSessionCache2() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); // HQL 查询不走缓存,还是会执行sql Query idQuery = session.createQuery("from UserDO where id = :id"); idQuery.setParameter("id", 1L); List<UserDO> userDOList = idQuery.list(); userDOList.forEach(System.out::println); // 再执行一次 HQL 查询还是不走缓存,会再执行一次sql userDOList = idQuery.list(); 例3、虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 @Test public void testSessionCache3() { Session session = HibernateUtils.openSession(); Query nameQuery = session.createQuery("from UserDO where name = :n"); nameQuery.setParameter("n", "李华6"); List<UserDO> userDOList = nameQuery.list(); userDOList.forEach(System.out::println); // 虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 System.out.println(session.get(UserDO.class, 6L)); 二级缓存(应用级缓存)二级缓存是全局性的,可以在多个 Session 中共享数据,二级缓存称为是 SessionFactory 级缓存,或应用级缓存。二级缓存默认没有开启,需要手动配置缓存实现(EhCache等)才可以使用。开启二级缓存后,根据id查询数据时,会先在一级缓存查询,没有再去二级缓存,还没有再去数据库查。在 Spring Data 中可通过 javax.persistence.Cacheable 等注解使用。 注意:一级缓存和二级缓存都是只在根据 ID 查询对象时起作用,并不对全部查询生效。 例1、默认二级缓存是不开启的,两个Session分别查id=1的数据,会执行两次sql @Test public void testSecondLevelCache() { Session session1 = HibernateUtils.openSession(); System.out.println(session1.get(UserDO.class, 1L)); Session session2 = HibernateUtils.openSession(); System.out.println(session2.get(UserDO.class, 1L)); 开启二级缓存1、打开二级缓存开关,测试过程中发现 use_second_level_cache 开关默认是开启的 <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.internal.EhCacheRegionFactory</property> 或在 properties 文件中配置: hibernate.cache.use_second_level_cache=true hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory 或者在 Spring-Boot yml 配置: spring: properties: hibernate: cache: use_second_level_cache: true region: factory_class: org.hibernate.cache.jcache.JCacheRegionFactory provider_configuration_file_resource_path: ehcache.xml 2、二级缓存需要在各个实体上单独开启,注解需要二级缓存的实体 import javax.persistence.Cacheable; import org.hibernate.annotations.CacheConcurrencyStrategy; @Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") private long id; 缓存并发策略READ_ONLY 只适用于只读的实体/表NONSTRICT_READ_WRITE 事务提交后会更新缓存,非强一致性,可能读到脏数据,适用于可容忍一定非一致性的数据。READ_WRITE 保证强一致性,缓存实体被更新时会加一个软锁,更新提交后软锁释放。访问加了软锁的缓存实体的并发请求会直接从数据库取数据。TRANSACTIONAL 缓存与数据库数据之间加 XA 分布式事务,保证两者数据一致性。 Hibernate Second-Level Cachehttps://www.baeldung.com/hibernate-second-level-cache 查询(Query)缓存Hibernate 的一、二级缓存都是根据对象 id 来查找时才起作用,如果需要缓存任意查询条件,就需要用到查询缓存。 Hibernate 配置开启 query 缓存 <property name="hibernate.cache.use_query_cache">true</property> 使用 Spring-Data-Jpa 时可在 application.yml 中配置开启 spring: properties: hibernate: cache: use_query_cache: true Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
https://www.cnblogs.com/xiaoluo501395377/p/3380270.htmlhttps://www.baeldung.com/hibernate-session-object-states
一级缓存的生命周期和 Session 一致,也称为 Session 级缓存,或者事务级缓存。一级缓存时默认开启的,不可关闭。
查询:当在同一个 Session 中查询同一个对象时,Hibernate 不会再次与数据库交互,直接从 Session 缓存中取。更新:save/update 更新对象时,Hibernate 不会立即发出 SQL 将这个数据存到数据库,而是将它放在了 Session 的一级缓存中,直到调用 flush() 或提交事务时才会一并存到数据库。
例1、下面两次根据ID查询user,只会执行一次select语句,第二次会从 Session 缓存中获取
@Test public void testSessionCache1() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); System.out.println(session.get(UserDO.class, 1L)); // 有缓存,只执行一次查询 例2、HQL 查询不使用 Session 缓存 @Test public void testSessionCache2() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); // HQL 查询不走缓存,还是会执行sql Query idQuery = session.createQuery("from UserDO where id = :id"); idQuery.setParameter("id", 1L); List<UserDO> userDOList = idQuery.list(); userDOList.forEach(System.out::println); // 再执行一次 HQL 查询还是不走缓存,会再执行一次sql userDOList = idQuery.list(); 例3、虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 @Test public void testSessionCache3() { Session session = HibernateUtils.openSession(); Query nameQuery = session.createQuery("from UserDO where name = :n"); nameQuery.setParameter("n", "李华6"); List<UserDO> userDOList = nameQuery.list(); userDOList.forEach(System.out::println); // 虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 System.out.println(session.get(UserDO.class, 6L)); 二级缓存(应用级缓存)二级缓存是全局性的,可以在多个 Session 中共享数据,二级缓存称为是 SessionFactory 级缓存,或应用级缓存。二级缓存默认没有开启,需要手动配置缓存实现(EhCache等)才可以使用。开启二级缓存后,根据id查询数据时,会先在一级缓存查询,没有再去二级缓存,还没有再去数据库查。在 Spring Data 中可通过 javax.persistence.Cacheable 等注解使用。 注意:一级缓存和二级缓存都是只在根据 ID 查询对象时起作用,并不对全部查询生效。 例1、默认二级缓存是不开启的,两个Session分别查id=1的数据,会执行两次sql @Test public void testSecondLevelCache() { Session session1 = HibernateUtils.openSession(); System.out.println(session1.get(UserDO.class, 1L)); Session session2 = HibernateUtils.openSession(); System.out.println(session2.get(UserDO.class, 1L)); 开启二级缓存1、打开二级缓存开关,测试过程中发现 use_second_level_cache 开关默认是开启的 <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.internal.EhCacheRegionFactory</property> 或在 properties 文件中配置: hibernate.cache.use_second_level_cache=true hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory 或者在 Spring-Boot yml 配置: spring: properties: hibernate: cache: use_second_level_cache: true region: factory_class: org.hibernate.cache.jcache.JCacheRegionFactory provider_configuration_file_resource_path: ehcache.xml 2、二级缓存需要在各个实体上单独开启,注解需要二级缓存的实体 import javax.persistence.Cacheable; import org.hibernate.annotations.CacheConcurrencyStrategy; @Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") private long id; 缓存并发策略READ_ONLY 只适用于只读的实体/表NONSTRICT_READ_WRITE 事务提交后会更新缓存,非强一致性,可能读到脏数据,适用于可容忍一定非一致性的数据。READ_WRITE 保证强一致性,缓存实体被更新时会加一个软锁,更新提交后软锁释放。访问加了软锁的缓存实体的并发请求会直接从数据库取数据。TRANSACTIONAL 缓存与数据库数据之间加 XA 分布式事务,保证两者数据一致性。 Hibernate Second-Level Cachehttps://www.baeldung.com/hibernate-second-level-cache 查询(Query)缓存Hibernate 的一、二级缓存都是根据对象 id 来查找时才起作用,如果需要缓存任意查询条件,就需要用到查询缓存。 Hibernate 配置开启 query 缓存 <property name="hibernate.cache.use_query_cache">true</property> 使用 Spring-Data-Jpa 时可在 application.yml 中配置开启 spring: properties: hibernate: cache: use_query_cache: true Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
@Test public void testSessionCache1() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); System.out.println(session.get(UserDO.class, 1L)); // 有缓存,只执行一次查询 例2、HQL 查询不使用 Session 缓存
例2、HQL 查询不使用 Session 缓存
@Test public void testSessionCache2() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); // HQL 查询不走缓存,还是会执行sql Query idQuery = session.createQuery("from UserDO where id = :id"); idQuery.setParameter("id", 1L); List<UserDO> userDOList = idQuery.list(); userDOList.forEach(System.out::println); // 再执行一次 HQL 查询还是不走缓存,会再执行一次sql userDOList = idQuery.list(); 例3、虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 @Test public void testSessionCache3() { Session session = HibernateUtils.openSession(); Query nameQuery = session.createQuery("from UserDO where name = :n"); nameQuery.setParameter("n", "李华6"); List<UserDO> userDOList = nameQuery.list(); userDOList.forEach(System.out::println); // 虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 System.out.println(session.get(UserDO.class, 6L)); 二级缓存(应用级缓存)二级缓存是全局性的,可以在多个 Session 中共享数据,二级缓存称为是 SessionFactory 级缓存,或应用级缓存。二级缓存默认没有开启,需要手动配置缓存实现(EhCache等)才可以使用。开启二级缓存后,根据id查询数据时,会先在一级缓存查询,没有再去二级缓存,还没有再去数据库查。在 Spring Data 中可通过 javax.persistence.Cacheable 等注解使用。 注意:一级缓存和二级缓存都是只在根据 ID 查询对象时起作用,并不对全部查询生效。 例1、默认二级缓存是不开启的,两个Session分别查id=1的数据,会执行两次sql @Test public void testSecondLevelCache() { Session session1 = HibernateUtils.openSession(); System.out.println(session1.get(UserDO.class, 1L)); Session session2 = HibernateUtils.openSession(); System.out.println(session2.get(UserDO.class, 1L)); 开启二级缓存1、打开二级缓存开关,测试过程中发现 use_second_level_cache 开关默认是开启的 <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.internal.EhCacheRegionFactory</property> 或在 properties 文件中配置: hibernate.cache.use_second_level_cache=true hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory 或者在 Spring-Boot yml 配置: spring: properties: hibernate: cache: use_second_level_cache: true region: factory_class: org.hibernate.cache.jcache.JCacheRegionFactory provider_configuration_file_resource_path: ehcache.xml 2、二级缓存需要在各个实体上单独开启,注解需要二级缓存的实体 import javax.persistence.Cacheable; import org.hibernate.annotations.CacheConcurrencyStrategy; @Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") private long id; 缓存并发策略READ_ONLY 只适用于只读的实体/表NONSTRICT_READ_WRITE 事务提交后会更新缓存,非强一致性,可能读到脏数据,适用于可容忍一定非一致性的数据。READ_WRITE 保证强一致性,缓存实体被更新时会加一个软锁,更新提交后软锁释放。访问加了软锁的缓存实体的并发请求会直接从数据库取数据。TRANSACTIONAL 缓存与数据库数据之间加 XA 分布式事务,保证两者数据一致性。 Hibernate Second-Level Cachehttps://www.baeldung.com/hibernate-second-level-cache 查询(Query)缓存Hibernate 的一、二级缓存都是根据对象 id 来查找时才起作用,如果需要缓存任意查询条件,就需要用到查询缓存。 Hibernate 配置开启 query 缓存 <property name="hibernate.cache.use_query_cache">true</property> 使用 Spring-Data-Jpa 时可在 application.yml 中配置开启 spring: properties: hibernate: cache: use_query_cache: true Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
@Test public void testSessionCache2() { Session session = HibernateUtils.openSession(); System.out.println(session.get(UserDO.class, 1L)); // HQL 查询不走缓存,还是会执行sql Query idQuery = session.createQuery("from UserDO where id = :id"); idQuery.setParameter("id", 1L); List<UserDO> userDOList = idQuery.list(); userDOList.forEach(System.out::println); // 再执行一次 HQL 查询还是不走缓存,会再执行一次sql userDOList = idQuery.list(); 例3、虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取
例3、虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取
@Test public void testSessionCache3() { Session session = HibernateUtils.openSession(); Query nameQuery = session.createQuery("from UserDO where name = :n"); nameQuery.setParameter("n", "李华6"); List<UserDO> userDOList = nameQuery.list(); userDOList.forEach(System.out::println); // 虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 System.out.println(session.get(UserDO.class, 6L)); 二级缓存(应用级缓存)二级缓存是全局性的,可以在多个 Session 中共享数据,二级缓存称为是 SessionFactory 级缓存,或应用级缓存。二级缓存默认没有开启,需要手动配置缓存实现(EhCache等)才可以使用。开启二级缓存后,根据id查询数据时,会先在一级缓存查询,没有再去二级缓存,还没有再去数据库查。在 Spring Data 中可通过 javax.persistence.Cacheable 等注解使用。 注意:一级缓存和二级缓存都是只在根据 ID 查询对象时起作用,并不对全部查询生效。 例1、默认二级缓存是不开启的,两个Session分别查id=1的数据,会执行两次sql @Test public void testSecondLevelCache() { Session session1 = HibernateUtils.openSession(); System.out.println(session1.get(UserDO.class, 1L)); Session session2 = HibernateUtils.openSession(); System.out.println(session2.get(UserDO.class, 1L)); 开启二级缓存1、打开二级缓存开关,测试过程中发现 use_second_level_cache 开关默认是开启的 <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.internal.EhCacheRegionFactory</property> 或在 properties 文件中配置: hibernate.cache.use_second_level_cache=true hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory 或者在 Spring-Boot yml 配置: spring: properties: hibernate: cache: use_second_level_cache: true region: factory_class: org.hibernate.cache.jcache.JCacheRegionFactory provider_configuration_file_resource_path: ehcache.xml 2、二级缓存需要在各个实体上单独开启,注解需要二级缓存的实体 import javax.persistence.Cacheable; import org.hibernate.annotations.CacheConcurrencyStrategy; @Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") private long id; 缓存并发策略READ_ONLY 只适用于只读的实体/表NONSTRICT_READ_WRITE 事务提交后会更新缓存,非强一致性,可能读到脏数据,适用于可容忍一定非一致性的数据。READ_WRITE 保证强一致性,缓存实体被更新时会加一个软锁,更新提交后软锁释放。访问加了软锁的缓存实体的并发请求会直接从数据库取数据。TRANSACTIONAL 缓存与数据库数据之间加 XA 分布式事务,保证两者数据一致性。 Hibernate Second-Level Cachehttps://www.baeldung.com/hibernate-second-level-cache 查询(Query)缓存Hibernate 的一、二级缓存都是根据对象 id 来查找时才起作用,如果需要缓存任意查询条件,就需要用到查询缓存。 Hibernate 配置开启 query 缓存 <property name="hibernate.cache.use_query_cache">true</property> 使用 Spring-Data-Jpa 时可在 application.yml 中配置开启 spring: properties: hibernate: cache: use_query_cache: true Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
@Test public void testSessionCache3() { Session session = HibernateUtils.openSession(); Query nameQuery = session.createQuery("from UserDO where name = :n"); nameQuery.setParameter("n", "李华6"); List<UserDO> userDOList = nameQuery.list(); userDOList.forEach(System.out::println); // 虽然 HQL 查询不走缓存,但是会将数据放入Session缓存,第二次根据id get查询直接从缓存取 System.out.println(session.get(UserDO.class, 6L));
二级缓存(应用级缓存)二级缓存是全局性的,可以在多个 Session 中共享数据,二级缓存称为是 SessionFactory 级缓存,或应用级缓存。二级缓存默认没有开启,需要手动配置缓存实现(EhCache等)才可以使用。开启二级缓存后,根据id查询数据时,会先在一级缓存查询,没有再去二级缓存,还没有再去数据库查。在 Spring Data 中可通过 javax.persistence.Cacheable 等注解使用。 注意:一级缓存和二级缓存都是只在根据 ID 查询对象时起作用,并不对全部查询生效。 例1、默认二级缓存是不开启的,两个Session分别查id=1的数据,会执行两次sql @Test public void testSecondLevelCache() { Session session1 = HibernateUtils.openSession(); System.out.println(session1.get(UserDO.class, 1L)); Session session2 = HibernateUtils.openSession(); System.out.println(session2.get(UserDO.class, 1L)); 开启二级缓存1、打开二级缓存开关,测试过程中发现 use_second_level_cache 开关默认是开启的 <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.internal.EhCacheRegionFactory</property> 或在 properties 文件中配置: hibernate.cache.use_second_level_cache=true hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory 或者在 Spring-Boot yml 配置: spring: properties: hibernate: cache: use_second_level_cache: true region: factory_class: org.hibernate.cache.jcache.JCacheRegionFactory provider_configuration_file_resource_path: ehcache.xml 2、二级缓存需要在各个实体上单独开启,注解需要二级缓存的实体 import javax.persistence.Cacheable; import org.hibernate.annotations.CacheConcurrencyStrategy; @Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") private long id; 缓存并发策略READ_ONLY 只适用于只读的实体/表NONSTRICT_READ_WRITE 事务提交后会更新缓存,非强一致性,可能读到脏数据,适用于可容忍一定非一致性的数据。READ_WRITE 保证强一致性,缓存实体被更新时会加一个软锁,更新提交后软锁释放。访问加了软锁的缓存实体的并发请求会直接从数据库取数据。TRANSACTIONAL 缓存与数据库数据之间加 XA 分布式事务,保证两者数据一致性。 Hibernate Second-Level Cachehttps://www.baeldung.com/hibernate-second-level-cache 查询(Query)缓存Hibernate 的一、二级缓存都是根据对象 id 来查找时才起作用,如果需要缓存任意查询条件,就需要用到查询缓存。 Hibernate 配置开启 query 缓存 <property name="hibernate.cache.use_query_cache">true</property> 使用 Spring-Data-Jpa 时可在 application.yml 中配置开启 spring: properties: hibernate: cache: use_query_cache: true Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
二级缓存是全局性的,可以在多个 Session 中共享数据,二级缓存称为是 SessionFactory 级缓存,或应用级缓存。二级缓存默认没有开启,需要手动配置缓存实现(EhCache等)才可以使用。开启二级缓存后,根据id查询数据时,会先在一级缓存查询,没有再去二级缓存,还没有再去数据库查。在 Spring Data 中可通过 javax.persistence.Cacheable 等注解使用。
javax.persistence.Cacheable
注意:一级缓存和二级缓存都是只在根据 ID 查询对象时起作用,并不对全部查询生效。
例1、默认二级缓存是不开启的,两个Session分别查id=1的数据,会执行两次sql
@Test public void testSecondLevelCache() { Session session1 = HibernateUtils.openSession(); System.out.println(session1.get(UserDO.class, 1L)); Session session2 = HibernateUtils.openSession(); System.out.println(session2.get(UserDO.class, 1L)); 开启二级缓存1、打开二级缓存开关,测试过程中发现 use_second_level_cache 开关默认是开启的 <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.internal.EhCacheRegionFactory</property> 或在 properties 文件中配置: hibernate.cache.use_second_level_cache=true hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory 或者在 Spring-Boot yml 配置: spring: properties: hibernate: cache: use_second_level_cache: true region: factory_class: org.hibernate.cache.jcache.JCacheRegionFactory provider_configuration_file_resource_path: ehcache.xml 2、二级缓存需要在各个实体上单独开启,注解需要二级缓存的实体 import javax.persistence.Cacheable; import org.hibernate.annotations.CacheConcurrencyStrategy; @Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") private long id; 缓存并发策略READ_ONLY 只适用于只读的实体/表NONSTRICT_READ_WRITE 事务提交后会更新缓存,非强一致性,可能读到脏数据,适用于可容忍一定非一致性的数据。READ_WRITE 保证强一致性,缓存实体被更新时会加一个软锁,更新提交后软锁释放。访问加了软锁的缓存实体的并发请求会直接从数据库取数据。TRANSACTIONAL 缓存与数据库数据之间加 XA 分布式事务,保证两者数据一致性。 Hibernate Second-Level Cachehttps://www.baeldung.com/hibernate-second-level-cache 查询(Query)缓存Hibernate 的一、二级缓存都是根据对象 id 来查找时才起作用,如果需要缓存任意查询条件,就需要用到查询缓存。 Hibernate 配置开启 query 缓存 <property name="hibernate.cache.use_query_cache">true</property> 使用 Spring-Data-Jpa 时可在 application.yml 中配置开启 spring: properties: hibernate: cache: use_query_cache: true Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
@Test public void testSecondLevelCache() { Session session1 = HibernateUtils.openSession(); System.out.println(session1.get(UserDO.class, 1L)); Session session2 = HibernateUtils.openSession(); System.out.println(session2.get(UserDO.class, 1L));
开启二级缓存1、打开二级缓存开关,测试过程中发现 use_second_level_cache 开关默认是开启的 <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.internal.EhCacheRegionFactory</property> 或在 properties 文件中配置: hibernate.cache.use_second_level_cache=true hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory 或者在 Spring-Boot yml 配置: spring: properties: hibernate: cache: use_second_level_cache: true region: factory_class: org.hibernate.cache.jcache.JCacheRegionFactory provider_configuration_file_resource_path: ehcache.xml 2、二级缓存需要在各个实体上单独开启,注解需要二级缓存的实体 import javax.persistence.Cacheable; import org.hibernate.annotations.CacheConcurrencyStrategy; @Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") private long id; 缓存并发策略READ_ONLY 只适用于只读的实体/表NONSTRICT_READ_WRITE 事务提交后会更新缓存,非强一致性,可能读到脏数据,适用于可容忍一定非一致性的数据。READ_WRITE 保证强一致性,缓存实体被更新时会加一个软锁,更新提交后软锁释放。访问加了软锁的缓存实体的并发请求会直接从数据库取数据。TRANSACTIONAL 缓存与数据库数据之间加 XA 分布式事务,保证两者数据一致性。 Hibernate Second-Level Cachehttps://www.baeldung.com/hibernate-second-level-cache 查询(Query)缓存Hibernate 的一、二级缓存都是根据对象 id 来查找时才起作用,如果需要缓存任意查询条件,就需要用到查询缓存。 Hibernate 配置开启 query 缓存 <property name="hibernate.cache.use_query_cache">true</property> 使用 Spring-Data-Jpa 时可在 application.yml 中配置开启 spring: properties: hibernate: cache: use_query_cache: true Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
1、打开二级缓存开关,测试过程中发现 use_second_level_cache 开关默认是开启的
use_second_level_cache
<property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.internal.EhCacheRegionFactory</property> 或在 properties 文件中配置: hibernate.cache.use_second_level_cache=true hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory 或者在 Spring-Boot yml 配置: spring: properties: hibernate: cache: use_second_level_cache: true region: factory_class: org.hibernate.cache.jcache.JCacheRegionFactory provider_configuration_file_resource_path: ehcache.xml 2、二级缓存需要在各个实体上单独开启,注解需要二级缓存的实体 import javax.persistence.Cacheable; import org.hibernate.annotations.CacheConcurrencyStrategy; @Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") private long id; 缓存并发策略READ_ONLY 只适用于只读的实体/表NONSTRICT_READ_WRITE 事务提交后会更新缓存,非强一致性,可能读到脏数据,适用于可容忍一定非一致性的数据。READ_WRITE 保证强一致性,缓存实体被更新时会加一个软锁,更新提交后软锁释放。访问加了软锁的缓存实体的并发请求会直接从数据库取数据。TRANSACTIONAL 缓存与数据库数据之间加 XA 分布式事务,保证两者数据一致性。 Hibernate Second-Level Cachehttps://www.baeldung.com/hibernate-second-level-cache 查询(Query)缓存Hibernate 的一、二级缓存都是根据对象 id 来查找时才起作用,如果需要缓存任意查询条件,就需要用到查询缓存。 Hibernate 配置开启 query 缓存 <property name="hibernate.cache.use_query_cache">true</property> 使用 Spring-Data-Jpa 时可在 application.yml 中配置开启 spring: properties: hibernate: cache: use_query_cache: true Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
<property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.internal.EhCacheRegionFactory</property> 或在 properties 文件中配置:
或在 properties 文件中配置:
hibernate.cache.use_second_level_cache=true hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory 或者在 Spring-Boot yml 配置: spring: properties: hibernate: cache: use_second_level_cache: true region: factory_class: org.hibernate.cache.jcache.JCacheRegionFactory provider_configuration_file_resource_path: ehcache.xml 2、二级缓存需要在各个实体上单独开启,注解需要二级缓存的实体 import javax.persistence.Cacheable; import org.hibernate.annotations.CacheConcurrencyStrategy; @Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") private long id; 缓存并发策略READ_ONLY 只适用于只读的实体/表NONSTRICT_READ_WRITE 事务提交后会更新缓存,非强一致性,可能读到脏数据,适用于可容忍一定非一致性的数据。READ_WRITE 保证强一致性,缓存实体被更新时会加一个软锁,更新提交后软锁释放。访问加了软锁的缓存实体的并发请求会直接从数据库取数据。TRANSACTIONAL 缓存与数据库数据之间加 XA 分布式事务,保证两者数据一致性。 Hibernate Second-Level Cachehttps://www.baeldung.com/hibernate-second-level-cache 查询(Query)缓存Hibernate 的一、二级缓存都是根据对象 id 来查找时才起作用,如果需要缓存任意查询条件,就需要用到查询缓存。 Hibernate 配置开启 query 缓存 <property name="hibernate.cache.use_query_cache">true</property> 使用 Spring-Data-Jpa 时可在 application.yml 中配置开启 spring: properties: hibernate: cache: use_query_cache: true Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
hibernate.cache.use_second_level_cache=true hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory 或者在 Spring-Boot yml 配置:
或者在 Spring-Boot yml 配置:
spring: properties: hibernate: cache: use_second_level_cache: true region: factory_class: org.hibernate.cache.jcache.JCacheRegionFactory provider_configuration_file_resource_path: ehcache.xml 2、二级缓存需要在各个实体上单独开启,注解需要二级缓存的实体 import javax.persistence.Cacheable; import org.hibernate.annotations.CacheConcurrencyStrategy; @Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") private long id; 缓存并发策略READ_ONLY 只适用于只读的实体/表NONSTRICT_READ_WRITE 事务提交后会更新缓存,非强一致性,可能读到脏数据,适用于可容忍一定非一致性的数据。READ_WRITE 保证强一致性,缓存实体被更新时会加一个软锁,更新提交后软锁释放。访问加了软锁的缓存实体的并发请求会直接从数据库取数据。TRANSACTIONAL 缓存与数据库数据之间加 XA 分布式事务,保证两者数据一致性。 Hibernate Second-Level Cachehttps://www.baeldung.com/hibernate-second-level-cache 查询(Query)缓存Hibernate 的一、二级缓存都是根据对象 id 来查找时才起作用,如果需要缓存任意查询条件,就需要用到查询缓存。 Hibernate 配置开启 query 缓存 <property name="hibernate.cache.use_query_cache">true</property> 使用 Spring-Data-Jpa 时可在 application.yml 中配置开启 spring: properties: hibernate: cache: use_query_cache: true Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
spring: properties: hibernate: cache: use_second_level_cache: true region: factory_class: org.hibernate.cache.jcache.JCacheRegionFactory provider_configuration_file_resource_path: ehcache.xml 2、二级缓存需要在各个实体上单独开启,注解需要二级缓存的实体
2、二级缓存需要在各个实体上单独开启,注解需要二级缓存的实体
import javax.persistence.Cacheable; import org.hibernate.annotations.CacheConcurrencyStrategy; @Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") private long id; 缓存并发策略READ_ONLY 只适用于只读的实体/表NONSTRICT_READ_WRITE 事务提交后会更新缓存,非强一致性,可能读到脏数据,适用于可容忍一定非一致性的数据。READ_WRITE 保证强一致性,缓存实体被更新时会加一个软锁,更新提交后软锁释放。访问加了软锁的缓存实体的并发请求会直接从数据库取数据。TRANSACTIONAL 缓存与数据库数据之间加 XA 分布式事务,保证两者数据一致性。 Hibernate Second-Level Cachehttps://www.baeldung.com/hibernate-second-level-cache 查询(Query)缓存Hibernate 的一、二级缓存都是根据对象 id 来查找时才起作用,如果需要缓存任意查询条件,就需要用到查询缓存。 Hibernate 配置开启 query 缓存 <property name="hibernate.cache.use_query_cache">true</property> 使用 Spring-Data-Jpa 时可在 application.yml 中配置开启 spring: properties: hibernate: cache: use_query_cache: true Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
import javax.persistence.Cacheable; import org.hibernate.annotations.CacheConcurrencyStrategy; @Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Foo { @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") private long id;
缓存并发策略READ_ONLY 只适用于只读的实体/表NONSTRICT_READ_WRITE 事务提交后会更新缓存,非强一致性,可能读到脏数据,适用于可容忍一定非一致性的数据。READ_WRITE 保证强一致性,缓存实体被更新时会加一个软锁,更新提交后软锁释放。访问加了软锁的缓存实体的并发请求会直接从数据库取数据。TRANSACTIONAL 缓存与数据库数据之间加 XA 分布式事务,保证两者数据一致性。 Hibernate Second-Level Cachehttps://www.baeldung.com/hibernate-second-level-cache 查询(Query)缓存Hibernate 的一、二级缓存都是根据对象 id 来查找时才起作用,如果需要缓存任意查询条件,就需要用到查询缓存。 Hibernate 配置开启 query 缓存 <property name="hibernate.cache.use_query_cache">true</property> 使用 Spring-Data-Jpa 时可在 application.yml 中配置开启 spring: properties: hibernate: cache: use_query_cache: true Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
READ_ONLY 只适用于只读的实体/表NONSTRICT_READ_WRITE 事务提交后会更新缓存,非强一致性,可能读到脏数据,适用于可容忍一定非一致性的数据。READ_WRITE 保证强一致性,缓存实体被更新时会加一个软锁,更新提交后软锁释放。访问加了软锁的缓存实体的并发请求会直接从数据库取数据。TRANSACTIONAL 缓存与数据库数据之间加 XA 分布式事务,保证两者数据一致性。
READ_ONLY
NONSTRICT_READ_WRITE
READ_WRITE
TRANSACTIONAL
Hibernate Second-Level Cachehttps://www.baeldung.com/hibernate-second-level-cache
Hibernate 的一、二级缓存都是根据对象 id 来查找时才起作用,如果需要缓存任意查询条件,就需要用到查询缓存。
Hibernate 配置开启 query 缓存
<property name="hibernate.cache.use_query_cache">true</property> 使用 Spring-Data-Jpa 时可在 application.yml 中配置开启 spring: properties: hibernate: cache: use_query_cache: true Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
<property name="hibernate.cache.use_query_cache">true</property> 使用 Spring-Data-Jpa 时可在 application.yml 中配置开启
使用 Spring-Data-Jpa 时可在 application.yml 中配置开启
spring: properties: hibernate: cache: use_query_cache: true Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
spring: properties: hibernate: cache: use_query_cache: true
Sessionsave()/persist() 区别persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。 对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO @Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
persist() 把一个瞬态的实例持久化,但是并”不保证”标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到 flush() 的时候。save() 把一个瞬态的实例持久化,保证立即返回一个标识符。如果需要运行 INSERT 来获取标识符(如 “identity” 而非 “sequence” 生成器),这个 INSERT 将立即执行,即使事务还未提交。经测试,主键生成策略是 IDENTITY 的实体,在 MySQL 上测试,发现 persist/save 都会立即执行 insert 语句(事务还未提交),然后实体都具有了主键 id。
对象id不为 null 时,persist() 方法会抛出 PersistenceException 异常javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.masikkk.common.jpa.UserDO
@Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存 @Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
@Test public void testPersistWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("persist持久化带id"); Exception e = Assertions.assertThrows(PersistenceException.class, () -> session.persist(userDO)); System.out.println(e); session.getTransaction().commit(); 对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存
对象id不为 null 时,save() 方法会忽略id,当做一个新对象正常保存
@Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体 get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
@Test public void testSaveWithId() { Session session = HibernateUtils.openSession(); session.beginTransaction(); UserDO userDO = new UserDO(); userDO.setId(20L); userDO.setName("save持久化带id"); session.save(userDO); session.getTransaction().commit(); 上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。 save带id的实体
上面的 user id 被设为 20,但 save 之后可以看到 id 是 14。
get()/load() 区别get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持 当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null @Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
get/load 都可以根据主键 id 从数据库中加载一个持久化对象。get/load 都会先从一级缓存中取,如果没有,get 会立即向数据库发请求,而 load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求,即 load 方法支持延迟加载策略,而 get 不支持
当数据库中不存在与 id 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null
@Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000] Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
@Test public void testGetNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.get(UserDO.class, 1000L); System.out.println(userDO); Assertions.assertNull(userDO); @Test public void testLoadNotExistId() { Session session = HibernateUtils.openSession(); UserDO userDO = session.load(UserDO.class, 2020L); // 此时不会抛异常 System.out.println(userDO); // 使用userDO时才真正去获取,才抛异常 load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000]
load() 是延迟加载的,session.load() 虽然查不到但不会立即抛异常,之后在真正使用 userDO 实体时才去查询并抛异常,如果之后一直不使用 userDO 就一直不抛异常org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.masikkk.common.jpa.UserDO#1000]
Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto 有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。 不配置此项,表示禁用自动建表功能 hibernate.dialect SQL方言hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/ Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。 之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam globally_quoted_identifiers 标识符加反引号将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true 其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号 Hibernate命名策略Hibernate隐式命名策略和物理命名策略hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。 在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。 JPA大写表名自动转换为小写提示表不存在有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名 @Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建,更新,验证数据库表结构。在 SpringBoot 中的配置项是 spring.jpa.properties.hibernate.hbm2ddl.auto
hibernate.hbm2ddl.auto
spring.jpa.properties.hibernate.hbm2ddl.auto
有几种配置:update 最常用的属性值,第一次加载 Hibernate 时创建数据表(前提是需要先有数据库),以后加载 Hibernate 时不会删除上一次生成的表,会根据实体更新,只新增字段,不会删除字段(即使实体中已经删除)。validate 每次加载 Hibernate 时都会验证数据表结构,只会和已经存在的数据表进行比较,根据 model 修改表结构,但不会创建新表。create 每次加载 Hibernate 时都会删除上一次生成的表(包括数据),然后重新生成新表,即使两次没有任何修改也会这样执行。适用于每次执行单测前清空数据库的场景。create-drop 每次加载 Hibernate 时都会生成表,但当 SessionFactory 关闭时,所生成的表将自动删除。none 不执行任何操作。将不会生成架构。适用于只读库。
update
validate
create
create-drop
none
不配置此项,表示禁用自动建表功能
hibernate.dialect SQL 方言在 SpringBoot 中的配置项是 spring.jpa.database-platform
hibernate.dialect
spring.jpa.database-platform
The MySQL Dialect refactoringhttps://in.relation.to/2017/02/20/mysql-dialect-refactoring/
Hibernate 5.2.8 开始重构了 MySQL 的 方言(Dialect) 枚举,MySQL55Dialect 之前的方言,包括 MySQLDialect,默认使用 MyISAM 引擎。从 MySQL55Dialect 开始,默认使用 InnoDB 引擎。
MySQL55Dialect
MySQLDialect
之前使用 MySQLInnoDBDialect 方言,可以改为 MySQLDialect 加 hibernate.dialect.storage_engine=innodb之前使用 MySQL5InnoDBDialect 方言,可以改为 MySQL5Dialect 加 hibernate.dialect.storage_engine=innodb
MySQLInnoDBDialect
hibernate.dialect.storage_engine=innodb
MySQL5InnoDBDialect
MySQL5Dialect
Hibernate: Create Mysql InnoDB tables instead of MyISAMhttps://stackoverflow.com/questions/1459265/hibernate-create-mysql-innodb-tables-instead-of-myisam
将 SQL 中的标识符(表名,列名等)全部用 反引号(MySQL) 括起来,可解决表名、列名和 MySQL 标识符冲突的问题。spring.jpa.properties.hibernate.globally_quoted_identifiers=true
spring.jpa.properties.hibernate.globally_quoted_identifiers=true
其实是根据 spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 配置的具体 SQL 方言来决定用什么符号将标识符括起来,比如 MySQL 中是反引号,达梦数据库中是双引号
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
hibernate 5.1 之前,命名策略通过 NamingStrategy 接口实现,但是 5.1 之后该接口已废弃。hibernate 5.1 之后,命名策略通过 隐式命名策略 接口 ImplicitNameSource 和 物理命名策略 接口 PhysicalNamingStrategy 共同作用实现。
NamingStrategy
ImplicitNameSource
PhysicalNamingStrategy
在 Spring 中使用时通过下面两个步骤来确定:第一步:如果我们没有使用 @Table 或 @Column 指定了表或字段的名称,则由 SpringImplicitNamingStrategy 为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy 不起作用。第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名 SpringPhysicalNamingStrategy 都会被调用。
SpringImplicitNamingStrategy
SpringPhysicalNamingStrategy
有个全大写的表名 MYTABLE, 实体类如下,通过 @Table 注解的 name 属性指定了大写的表名
@Table
@Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
@Data @Entity @Table(name = "MYTABLE") public class MYTABLEDO { 运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。 原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是
运行报错,提示找不到小写的表名com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘mydb.mytable’ doesn’t exist貌似 jpa 的默认表命名策略是都转为小写表名。
原因:spring data jpa 是基于 hibernate 5.0 , 而 Hibernate 5 关于数据库命名策略的配置与之前版本略有不同:不再支持早期的 hibernate.ejb.naming_strategy,而是改为两个配置项分别控制命名策略:hibernate.physical_naming_strategyhibernate.implicit_naming_strategy在 spring 中的配置项是
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法: import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。 physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 解决方法二:1 重写命名策略中改表名为小写的方法:
implicit-strategy 负责模型对象层次的处理,将对象模型处理为逻辑名称physical-strategy 负责映射成真实的数据名称的处理,将上述的逻辑名称处理为物理名称。当没有使用 @Table 和 @Column 注解时,implicit-strategy 配置项才会被使用,当对象模型中已经指定 @Table 和 @Column 时,implicit-strategy 并不会起作用。physical-strategy 一定会被应用,与对象模型中是否显式地指定列名或者已经被隐式决定无关。
physical-strategy 策略常用的两个实现有org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 这个是 Spring data jpa 的默认数据库命名策略。org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
解决方法一:可以在 springboot 项目中配置文件内加上配置行,设置命名为 无修改命名策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
解决方法二:1 重写命名策略中改表名为小写的方法:
import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy 解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; * 重写 hibernate 对于命名策略中改表名大写为小写的方法 public class MySQLUpperCaseStrategy extends PhysicalNamingStrategyStandardImpl { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) { String tableName = name.getText().toUpperCase(); return name.toIdentifier(tableName); 2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy
2 在对应配置文件中 使用自己实现的策略spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy
spring.jpa.hibernate.naming.physical-strategy=com.xxx.xxx.util.MySQLUpperCaseStrategy
解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082 当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787 JPA实现根据SpringBoot命令行参数动态设置表名背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277 3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559 最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。 完整步骤:1 自定义物理命名策略 @Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
解决 springboot + JPA + MySQL 表名全大写 出现 “表不存在” 问题(Table ‘XXX.xxx’ doesn’t exist)https://blog.csdn.net/jiangyu1013/article/details/80409082
当JPA遇上MySQL表名全大写+全小写+驼峰+匈牙利四风格https://blog.51cto.com/yerikyu/2440787
背景:使用 JPA 操作的一个表名经常变化(表结构不变)的表,每过一段时间就做一次数据升级,升级时表名会变,但表结构不变,使用 @Table(name = "xxx") 注解实体 bean 来做映射。表名变化后可以改配置参数重启,但尽量不要改代码,因为改代码还得重新发包。有如下几种方案:1 JPA 中通过 @Query 手动拼 sql,这样不仅不需要重启,利用配置项字典表还能运行中动态改表名。但不想这样做,都使用 JPA 了还手动拼sql,太麻烦。2 Hibernate 拦截器改表名,只是偶然搜到了能这么做,没实现过。Hibernate 拦截器的使用–动态表名https://my.oschina.net/cloudcross/blog/831277
@Table(name = "xxx")
@Query
3 自定义物理命名策略 SpringPhysicalNamingStrategy,在其中读取配置项,修改指定的表名,参考下面这篇文章。我完全照着这篇文章实现的话,总是无法成功,每次运行到 toPhysicalTableName() 方法时,之前注入的 parser 就变为 null 了,后来仔细看了看,发现被 ApplicationContextAware 回调的,和运行 toPhysicalTableName() 方法的,是两个不同的 MySpringPhysicalNamingStrategy 实例,导致无法读取配置。Spring Data JPA自定义实现动态表名映射https://blog.csdn.net/u014229347/article/details/88892559
toPhysicalTableName()
ApplicationContextAware
MySpringPhysicalNamingStrategy
最后改了改实现,简化为在命令行参数中 -Dtable.name=xxx 配置表名,在自定义命名策略中直接 System.getProperty("table.name") 读取命令行参数。
-Dtable.name=xxx
System.getProperty("table.name")
完整步骤:1 自定义物理命名策略
@Slf4j @Component public class ConfigurableNamingStrategy extends SpringPhysicalNamingStrategy { @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { if (StringUtils.equals(name.getText(), "table_name_placeholder")) { String tableName = System.getProperty("table.name"); log.info("新表名: {}", tableName); return Identifier.toIdentifier(tableName); } else { // 其他表不变 return super.toPhysicalTableName(name, jdbcEnvironment); 2 实体上直接注解为假的表名占位符 @Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
2 实体上直接注解为假的表名占位符
@Data @Entity @Table(name = "table_name_placeholder") public class MyDO { @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "JDBC") private Long id; 3 Spring 配置文件中设置自定义命名策略 spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
3 Spring 配置文件中设置自定义命名策略
spring.jpa.hibernate.naming.physical-strategy=com.masikkk.persistence.config.ConfigurableNamingStrategy 4 SpringBoot 启动参数中添加表名配置项 nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
4 SpringBoot 启动参数中添加表名配置项
nohup java -jar -Dtable.name=xxx myapp.jar & 注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到 Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191 Hibernate 对象状态Hibernate 对象三种状态Transient 临时态/瞬时态Persistent 持久态Detached 游离态/脱管态如何判断一个Hibernate对象的状态?Hibernate 对象状态转换图Hibernate 对象状态与操作示例save() 之后对象就有主键ID多次更新只最后提交时发出sql事务提交后持久态对象的修改自动更新到数据库游离态对象持久态对象无法修改IDHibernate 缓存一级缓存(Session级缓存)二级缓存(应用级缓存)开启二级缓存缓存并发策略查询(Query)缓存Sessionsave()/persist() 区别get()/load() 区别Hibernate 配置hibernate.hbm2ddl.auto 自动建表hibernate.dialect SQL方言Hibernate 5.2.8 之后 MySQLDialect DDL 默认使用 MyISAM 引擎globally_quoted_identifiers 标识符加反引号Hibernate命名策略Hibernate隐式命名策略和物理命名策略JPA大写表名自动转换为小写提示表不存在JPA实现根据SpringBoot命令行参数动态设置表名
注意,这种 -D System 参数必须放在 myapp.jar 之前才能被 System.getProperty() 读取到
-D
System.getProperty()
Hibernate 自定义表名映射https://segmentfault.com/a/1190000015305191