hibernate-基础 hibernate 作为 ORM 解决方案,非常强调领域模型的概念,是非侵入式的,Hibernate不要求持久化类实现任何接口或继承任何类,POJO即可
hibernate 官方文档-https://docs.jboss.org/hibernate/orm/5.6/userguide/html_single/Hibernate_User_Guide.html
hibernate 总体的架构图 如下图所示,hibernate 实现了java persistence api(JPA),同时也自己扩展了接口 hibernate native api
graph LR
userDataAccessLayer(user data access layer)
javaPersistenceApi(java persistence api)
hibernateNativeApi(hibernate native api)
hibernate
jdbc
database
userDataAccessLayer -.-> javaPersistenceApi
userDataAccessLayer -.-> hibernateNativeApi
subgraph hibernate-core
javaPersistenceApi -.-> hibernate
hibernateNativeApi -.-> hibernate
end
subgraph database-jdbc
hibernate-.->jdbc
jdbc-.->database
end
style hibernate-core fill: #f9f, stroke: #333, stroke-width: 1px;
style database-jdbc fill: #ee9f, stroke: #333, stroke-width: 1px;
Hibernate API 最基础的 API
SessionFactory(会话工厂) 是一个线程安全的,immutable(终态的),它是一个代理,表示应用程序域,SessionFactory 的建立代价很大,所以一个 应用只能有一个 SessionFactory,SessionFactory 维护 ,Hibernate 的所有 Session(会话),二级缓冲,连接池,事务等等。如果应用需要使用Hibernate访问多个数据库,则需要对每一个数据库使用一个SessionFactory。
Session(会话) Session(会话)是一个单线程,短生命周期的对象,是按”Unit of Work(工作单元),Session 维护了一级缓存,在第一次发送SQL语句查询后第二次直接使用缓存中的数据,不会再发送SQL。除非session缓存被清空,平常的 crud 就通过它。session是一个轻量级对象,Session实例并不是线程安全的,因此应该被设计为每次只能在一个线程中使用。通常将每一个Session实例和一个数据库事务绑定,也就是说,每执行一个数据库事务,都应该先创建一个新的Session实例
Transaction(事务) 那么每个Session的操作,是一个独立的事务,是Hibernate的数据库事务接口,它对底层的事务接口做了封装,底层事务接口包括JDBC事务和JTA(Java Transaction API)事务
Query (接口) 是Hibernate的查询接口,用于向数据库查询对象,以及控制执行查询的过程。Query实例包装了一个HQL(Hibernate Query Language)查询语句,HQL查询语句与SQL查询语句有些相似,但HQL查询语句是面向对象的,它引用类名及类的属性名,而不是表名及表的字段名。 Query接口让你方便地对数据库及持久对象进行查询,它可以有两种表达方式:HQL语言或本地数据库的SQL语句。Query经常被用来绑定查询参数、限制查询记录数量,并最终执行查询操作。
Criteria(接口)
Configuration(配置类) Configuration 类的作用是对Hibernate 进行配置,以及对它进行启动。在Hibernate 的启动过程中,Configuration 类的实例首先定位映射文档的位置,读取这些配置,然后创建一个SessionFactory对象。虽然Configuration 类在整个Hibernate 项目中只扮演着一个很小的角色,但它是启动hibernate 时所遇到的第一个对象。
如图中的根接口都是在 jpa 中定义的
classDiagram
I_EntityManagerFactory <|-- I_SessionFactory
I_SessionFactory <|.. C_SessionFactoryImpl
I_EntityManager <|-- I_Session
I_Session <|.. C_SessionImpl
I_EntityTransaction <|-- I_Transaction
I_Transaction <|.. C_TransactionImpl
环境准备 mysql 测试通过,postgres 用的是 42.5.1 版本 测试没有通过 ,无法加载 postgres drive
1 2 3 4 5 6 7 8 9 10 11 12 13 <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.30</version > </dependency > <dependency > <groupId > org.hibernate</groupId > <artifactId > hibernate-core</artifactId > <version > 5.6.14.Final</version > </dependency >
基础类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 class Config { public static Map.Entry<Object, EntityEntry>[] getManagedEntities(PersistenceContext context) { var map = context.reentrantSafeEntityEntries(); return map; } public void extracted (Consumer<Configuration> configurer, Consumer<Session> handler) { var cfg = new Configuration (); cfg.setProperty("hibernate.dialect" , "org.hibernate.dialect.MySQL8Dialect" ); cfg.setProperty("hibernate.connection.driver_class" , "com.mysql.cj.jdbc.Driver" ); cfg.setProperty("hibernate.connection.url" , "jdbc:mysql://localhost:3306/MyDB" ); cfg.setProperty("hibernate.connection.username" , "root" ); cfg.setProperty("hibernate.connection.password" , "root" ); cfg.setProperty("hibernate.show_sql" , "true" ); cfg.setProperty("format_sql" , "true" ); cfg.setProperty("use_sql_comments" , "true" ); cfg.setProperty("hibernate.hbm2ddl.auto" ,"update" ); cfg.setInterceptor(new MyInterceptor ()); configurer.accept(cfg); SessionFactory factory = null ; Session session = null ; var serviceRegistry=new StandardServiceRegistryBuilder ().applySettings(cfg.getProperties()).build(); try { factory = cfg.buildSessionFactory(serviceRegistry); var ii= factory.unwrap(SessionFactoryImplementor.class); ii.getServiceRegistry().getService(EventListenerRegistry.class).appendListeners(EventType.FLUSH,new MyEvent ()); session = factory.openSession(); if (session.isConnected()) { System.out.println("连接已通" ); handler.accept(session); } } catch (Exception ex) { ex.printStackTrace(); } finally { if (session != null ) { session.close(); } if (factory != null ) { factory.close(); } } } } class MyInterceptor extends EmptyInterceptor { @Override public boolean onFlushDirty (Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) { for (int i = 0 ; i < propertyNames.length; i++) { var type = types[i]; var newValue= currentState[i]; var oldValue= previousState[i]; System.out.println("Cargo 的属性:" +propertyNames[i] + ", 属性类型:" +type.getName()+ ", id:" +id+ ", old value:" +oldValue+ ", new value:" +newValue); } return false ; } }
简单的测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class Programmer { public static void main (String[] args) throws ClassNotFoundException, SQLException { var config = new Config (); config.extracted(cfg -> { cfg.addAnnotatedClass(Cargo.class); cfg.addAnnotatedClass(Person.class); }, session -> { session.beginTransaction(); var cargo = session.get(Cargo.class,1 ); var context = session.unwrap(SessionImplementor.class).getPersistenceContext(); System.out.println("管理的实体个数:" +context.getNumberOfManagedEntities()); Arrays.stream(Config.getManagedEntities(context)).forEach(p->{ System.out.println(p.getKey()); System.out.println(p.getValue()); }); session.getTransaction().commit(); }); }
脏数据 对被 session 管理的数据进行更改后的数据是脏数据,也就是和数据库的数据不同步了,当提交事务的时候,脏数据会自动同步到数据库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 session.beginTransaction(); var cargo = session.get(Cargo.class,1 );var context = session.unwrap(SessionImplementor.class).getPersistenceContext();System.out.println("管理的实体个数:" +context.getNumberOfManagedEntities()); Arrays.stream(Config.getManagedEntities(context)).forEach(p->{ System.out.println(p.getKey()); System.out.println(p.getValue()); }); System.out.println("数据是否改变" +session.isDirty()); cargo.setName("dirt" ); System.out.println("数据是否改变" +session.isDirty()); session.getTransaction().commit(); System.out.println("数据是否改变" +session.isDirty());
JPA 和 Hibernate API 通过 unwrap 互相转 1 2 3 var context = session.unwrap(SessionImplementor.class).getPersistenceContext();var entityManager = session.unwrap(EntityManager.class);var sessionFactory=session.getEntityManagerFactory().unwrap(SessionFactory.class);
对象的状态 session 的主要工作就是让实体对象在不同的状态之间进行互相切换
瞬时状态:实体-没有ID,没有与Session关联
持久化状态:实体-有ID,与Session关联,特点:持久化状态对象的任何改变都会同步到数据库中 在Hibernate中,持久对象是具有数据库标识且在Session中的实例,持久对象有一个主键值设为数据库标识符。持久对象可能来自瞬时对象被save或saveOrUpdate之后形成,也可能是通过get()或load()等方法从数据库中检索出来的对象。
游离态:实体-有ID,没有与Session关联.游离态实例表示曾经与某个持久化上下文发生过关联,不过那个上下文被关闭了。它拥有持久化标识,并且在数据库中通常还存在一个对应的行,只是它已经不在持久化层的管理之下。
删除状态
EntityEntry 上下文跟踪的实体信息对象 脏数据及对象的缓存 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 session.beginTransaction(); var context=session.unwrap(SessionImplementor.class).getPersistenceContext(); System.out.println("管理的实体个数:" +context.getNumberOfManagedEntities()); Arrays.stream(Config.getManagedEntities(context)).forEach(p->{ System.out.println(p.getKey()); System.out.println(p.getValue()); }); var cargo = session.get(Cargo.class,1 ); cargo.setName("tool" ); System.out.println("session 是否有脏数据:" +session.isDirty()); session.flush(); System.out.println("session 是否有脏数据:" +session.isDirty()); var cargo1 = session.get(Cargo.class,1 ); System.out.println(cargo.equals(cargo1)); System.out.println("管理的实体个数:" +context.getNumberOfManagedEntities()); Arrays.stream(Config.getManagedEntities(context)).forEach(p->{ System.out.println(p.getKey()); System.out.println(p.getValue()); }); session.getTransaction().commit();
清除管理的实体 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 session.beginTransaction(); var context=session.unwrap(SessionImplementor.class).getPersistenceContext();System.out.println("管理的实体个数:" +context.getNumberOfManagedEntities()); Arrays.stream(Config.getManagedEntities(context)).forEach(p->{ System.out.println(p.getKey()); System.out.println(p.getValue()); }); var cargo = session.get(Cargo.class,1 );cargo.setName("tools" ); System.out.println("session 是否有脏数据:" +session.isDirty()); session.clear(); System.out.println("管理的实体个数:" +context.getNumberOfManagedEntities()); Arrays.stream(Config.getManagedEntities(context)).forEach(p->{ System.out.println(p.getKey()); System.out.println(p.getValue()); }); var cargo1 = session.get(Cargo.class,1 );System.out.println(cargo1.getName()); System.out.println("是否是缓存:" +cargo.equals(cargo1)); System.out.println("管理的实体个数:" +context.getNumberOfManagedEntities()); Arrays.stream(Config.getManagedEntities(context)).forEach(p->{ System.out.println(p.getKey()); System.out.println(p.getValue()); });
evict 在 session 缓存中清除一个持久化对象 1 2 3 4 5 6 7 8 9 10 var cargo=session.get(Cargo.class,1 );session.evict(cargo); System.out.println("管理的实体个数:" +context.getNumberOfManagedEntities()); Arrays.stream(Config.getManagedEntities(context)).forEach(p->{ System.out.println(p.getKey()); System.out.println(p.getValue()); });
session 对象 get , load 区别 get 立即加载,load 延迟加载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 session.beginTransaction(); var context=session.unwrap(SessionImplementor.class).getPersistenceContext();System.out.println("管理的实体个数:" +context.getNumberOfManagedEntities()); Arrays.stream(Config.getManagedEntities(context)).forEach(p->{ System.out.println(p.getKey()); System.out.println(p.getValue()); }); var cargo1=session.get(Cargo.class,1 );var cargo2=session.load(Cargo.class,2 );System.out.println("管理的实体个数:" +context.getNumberOfManagedEntities()); Arrays.stream(Config.getManagedEntities(context)).forEach(p->{ System.out.println(p.getKey()); System.out.println(p.getValue()); }); System.out.println(cargo2); System.out.println("管理的实体个数:" +context.getNumberOfManagedEntities()); Arrays.stream(Config.getManagedEntities(context)).forEach(p->{ System.out.println(p.getKey()); System.out.println(p.getValue()); }); session.getTransaction().commit();
update , merge 区别 注意点 :
update 更新 dispatch 对象,此对象会变成持久对象 更新 persistent 对象没有意义 更新 transient 对象,如果此对象有id,更新后除了设定更新的字段,其他字段会重置成null,无id 不能执行会报错
merge 更新 transient 对象,如果此对象有id,数据库有记录就进行更新数据库,无记录插入数据 更新 persistent 对象没有意义
save , persist 区别 persist 不会立即执行sql,所以拿不到数据库返回的 id ,save 会立即执行 sql,可以立即拿到数据库返回的 id
批量更新 批量更新,并且不会再访问的对象。具体的做法是在处理完一个对象或小批量对象后,立刻调用flush()方法清理缓存,然后再调用clear()方法 或 evict 方法清空缓存
普通的批量更新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 session.beginTransaction(); var context=session.unwrap(SessionImplementor.class).getPersistenceContext(); System.out.println("管理的实体个数:" +context.getNumberOfManagedEntities()); Arrays.stream(Config.getManagedEntities(context)).forEach(p->{ System.out.println(p.getKey()); System.out.println(p.getValue()); }); for (int i = 1 ; i < 9 ; i++) { var cargo=session.get(Cargo.class,i); cargo.setName(LocalDateTime.now().toString()); session.flush(); session.evict(cargo); } System.out.println("管理的实体个数:" +context.getNumberOfManagedEntities()); Arrays.stream(Config.getManagedEntities(context)).forEach(p->{ System.out.println(p.getKey()); System.out.println(p.getValue()); }); session.getTransaction().commit(); });
通过 HQL 的批量操作,是在数据库完成 .scroll 方法返回的是游标,只有真正访问对象的时候才会去查询对象
1 2 3 4 5 6 7 8 9 var scroll= session.createQuery("from Cargo " ).scroll(ScrollMode.FORWARD_ONLY);while (scroll.next()){ var cargo = (Cargo) scroll.get()[0 ]; cargo.setName(LocalDateTime.now().toString()); session.flush(); session.clear(); }
spring data jpa 需要的依赖
父模块引入 spring-data-bom 方便子模块的版本管理
1 2 3 4 5 6 7 8 9 10 11 12 <dependencyManagement > <dependencies > <dependency > <groupId > org.springframework.data</groupId > <artifactId > spring-data-bom</artifactId > <version > 2021.1.10</version > <scope > import</scope > <type > pom</type > </dependency > </dependencies > </dependencyManagement >
子模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <dependency > <groupId > org.springframework.data</groupId > <artifactId > spring-data-jpa</artifactId > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.30</version > </dependency > <dependency > <groupId > com.zaxxer</groupId > <artifactId > HikariCP</artifactId > <version > 5.0.1</version > </dependency > <dependency > <groupId > org.hibernate</groupId > <artifactId > hibernate-core</artifactId > <version > 5.6.14.Final</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-autoconfigure</artifactId > <version > 2.7.5</version > </dependency >
实体类
1 2 3 4 5 6 7 8 9 10 11 12 @Entity @Getter @Setter public class Person { @Id @Column(name = "id", nullable = false) @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; @Column(name = "name", nullable = true) String name; }
spring data jpa 配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 @Component public class ConfigerDB { @Bean("dataSource") public DataSource getDataSource () { var dataSource = DataSourceBuilder.create(); dataSource.url("jdbc:mysql://localhost:3306/MyDB" ); dataSource.driverClassName("com.mysql.cj.jdbc.Driver" ); dataSource.username("root" ); dataSource.password("root" ); var pool = new HikariDataSource (); pool.setDataSource(dataSource.build()); return pool; } @Bean("jpaProperties") public JpaProperties getJpaProperties () { var jpaProperties = new JpaProperties () { }; jpaProperties.setShowSql(true ); jpaProperties.setProperties(new HashMap <>() { { put("hibernate.hbm2ddl.auto" , "update" ); put("hibernate.dialect" , "org.hibernate.dialect.MySQL8Dialect" ); put("hibernate.show_sql" , "true" ); } }); return jpaProperties; } @Bean("jpaVendorAdapter") public JpaVendorAdapter getJpaVendorAdapter () { return new HibernateJpaVendorAdapter (); } @Bean("myEntityManagerFactory") @Primary public LocalContainerEntityManagerFactoryBean getEntityManagerFactory ( DataSource dataSource, JpaProperties jpaProperties, JpaVendorAdapter jpaVendorAdapter) { var localContainerEntityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean (); localContainerEntityManagerFactoryBean.setDataSource(dataSource); localContainerEntityManagerFactoryBean.setJpaVendorAdapter(jpaVendorAdapter); localContainerEntityManagerFactoryBean.setJpaPropertyMap(jpaProperties.getProperties()); localContainerEntityManagerFactoryBean.setPersistenceUnitName("myPersistenceUnitName" ); localContainerEntityManagerFactoryBean.setPackagesToScan("com.hank.entityModel" ); return localContainerEntityManagerFactoryBean; } @Bean("jdbcTransactionManager") public JpaTransactionManager getJdbcTransactionManager (LocalContainerEntityManagerFactoryBean myEntityManagerFactory) { return new JpaTransactionManager (myEntityManagerFactory.getObject()); } }
按照 JPA 的方式使用 如下代理在 spring jpa 里可以完全按照 jpa 规范使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 @ComponentScan({"com.hank"}) @EnableTransactionManagement public class Programmer { public static void main (String[] args) throws Exception { var context = new AnnotationConfigApplicationContext (Programmer.class); { var localContainerEntityManagerFactoryBean = context.getBean(LocalContainerEntityManagerFactoryBean.class); { var entityManager = localContainerEntityManagerFactoryBean.getObject().createEntityManager(); var transaction=entityManager.getTransaction(); transaction.begin(); System.out.println(entityManager); var person= entityManager.find(Person.class,1 ); System.out.println(person.getName()); person.setName(LocalDateTime.now().toString()); transaction.commit(); entityManager.close(); } { var entityManager = localContainerEntityManagerFactoryBean.getObject().createEntityManager(); System.out.println(entityManager); var person= entityManager.find(Person.class,1 ); System.out.println(person.getName()); entityManager.close(); } } } }
测试 spring data 里的事务 注意点:
@PersistenceContext 获取 EntityManager 代理,是线程安全的
@PersistenceUnit 获取 EntityManagerFactory
测试用例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 @Component class Dao { @PersistenceContext(unitName = "myEntityManagerFactory") private EntityManager entityManager; @PersistenceUnit(unitName = "myEntityManagerFactory") private EntityManagerFactory entityManagerFactory; @Transactional("jdbcTransactionManager") public void func1 () { System.out.println(entityManager); var person = entityManager.find(Person.class, 1 ); person.setName(LocalDateTime.now().toString()); System.out.println("设置数值:" + person.getName()); entityManager.close(); } public void func2 () { System.out.println(entityManagerFactory); var entityManager = entityManagerFactory.createEntityManager(); System.out.println(entityManager); var transaction=entityManager.getTransaction(); transaction.begin(); var person = entityManager.find(Person.class, 1 ); System.out.println("读取数值:" + person.getName()); person.setName(LocalDateTime.now().toString()); System.out.println("设置数值:" + person.getName()); transaction.commit(); entityManager.close(); } public void func3 () { System.out.println(entityManagerFactory); var entityManager = entityManagerFactory.createEntityManager(); System.out.println(entityManager); var person = entityManager.find(Person.class, 1 ); System.out.println("读取数值:" + person.getName()); entityManager.close(); } }
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @ComponentScan({"com.hank"}) @EnableTransactionManagement public class Programmer { public static void main (String[] args) throws Exception { var context = new AnnotationConfigApplicationContext (Programmer.class); context.getBean(Dao.class).func1(); context.getBean(Dao.class).func2(); context.getBean(Dao.class).func3(); } }
spring data jpa 拓展 Repository 及其子类 Repository<T,ID> 位于Spring Data Common的lib里面,是Spring Data 里面做数据库操作的最底层的抽象接口、最顶级的父类,源码里面其实什么方法都没有,仅仅起到一个标识作用。管理域类以及域类的id类型作为类型参数,此接口主要作为标记接口捕获要使用的类型,并帮助你发现扩展此接口的接口。能够在类路径扫描期间发现扩展该类型的接口.Spring底层做动态代理的时候发现只要是它的子类或者实现类,都代表储存库操作
注意:
@NoRepositoryBean 在接口或对象上加此注解的 jpa 存储库,不会注入 spring ioc
@Repository 如果在加了 @NoRepositoryBean 接口的实现类注入 spring ioc ,要把 @Repository 加载类或接口上
为什么存储库的方法只用会有事务,因为 SimpleJpaRepository 实现类上加了注解 @Transactional
Repository 大类:
ReactiveCrudRepository,响应式编程,主要支持当前 NoSQL 方面的操作,因为这方面大部分操作都是分布式的,目前 Reactive 主要有 Cassandra、MongoDB、Redis 的实现。
CoroutineCrudRepository :为了支持 Kotlin 语法而实现的。
CrudRepository :JPA 相关的操作接口,也是我们主要用到的接口
更详细一点,我们需要掌握和使用到的7 大 Repository 接口如下所示:
Repository(org.springframework.data.repository),没有暴露任何方法;
CrudRepository(org.springframework.data.repository),简单的 Curd 方法;
PagingAndSortingRepository(org.springframework.data.repository),带分页和排序的方法;
QueryByExampleExecutor(org.springframework.data.repository.query),简单 Example 查询;
JpaRepository(org.springframework.data.jpa.repository),JPA 的扩展方法;
JpaSpecificationExecutor(org.springframework.data.jpa.repository),JpaSpecification 扩展查询;
两大 Repository 实现类:
SimpleJpaRepository(org.springframework.data.jpa.repository.support),JPA 所有接口的默认实现类;
第三方的 QueryDsl 在 JPA 和 spring data 的基础上扩展的第三方查询库:
接口 QueryDslPredicateExecutor(org.springframework.data.querydsl),QueryDsl 的封装
实现 QueryDslJpaRepository(org.springframework.data.jpa.repository.support),QueryDsl 的实现类
spring 扩展的方法 @NamedQuery、@Query和方法定义查询的对比:
Spring JPA里面的优先级,@Query > @NameQuery > 方法定义查询。
推荐使用的优先级:@Query > 方法定义查询 > @NameQuery。
相同点是都不支持动态条件查询。
方法的查询策略设置 @EnableJpaRepositories 注解来配置方法的查询策略,详细配置方法如下: @EnableJpaRepositories(queryLookupStrategy= QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND)
其中,QueryLookupStrategy.Key 的值共 3 个,具体如下:
Create:直接根据方法名进行创建,规则是根据方法名称的构造进行尝试,一般的方法是从方法名中删除给定的一组已知前缀,并解析该方法的其余部分。如果方法名不符合规则, 启动的时候会报异常,这种情况可以理解为,即使配置了 @Query 也是没有用的。
USE_DECLARED_QUERY:声明方式创建,启动的时候会尝试找到一个声明的查询,如果没有找到将抛出一个异常,可以理解为必须配置 @Query 或 @NameQuery
CREATE_IF_NOT_FOUND:这个是默认的,除非有特殊需求,可以理解为这是以上 2 种方式的兼容版。先用声明方式(@Query 或 @NameQuery)进行查找,如果没有找到与方法相 匹配的查询,那用 Create 的方法名创建规则创建一个查询;这两者都不满足的情况下,启动就会报错。
通过 @EnableJpaRepositories 注解需要提供 Repositories 包路径,entityManagerFactory 的beanName,transactionManager 的 beanName 此注解会扫描 Repository 及其子接口,并为其生成代理类,我们操作的是代理类
Declared Queries -方法定义查询 ,如果返回实体,不能返回实体的部分属性,而是全部属性 方法定义查询的方法要定义在仓储接口里,有代理类用相应的接口的实现类去实现接口内自定义方法
1 2 3 4 5 6 7 8 9 10 @Indexed public interface Repository <T, ID> {} public interface MyPersonRepository extends Repository <Person, Integer> { public Person findDistinctByName (String name) ; }
这种方法个人感觉不好用,属于死记硬背的做法,而且写错也没办法在编译的时候,检查出错误
属性表达式(Property Expressions)(findByAddressZipCode) 只能引用托管(泛化)实体的直接属性,如前一个示例所示。在查询创建时,你已经确保解析的属性是托管实体的属性。同时,还可以通过遍历嵌套属性定义约束。假设一个Person实体对象里面有一个Address属性里面包含一个ZipCode属性。在这种情况下,方法名为:
1 2 3 List<Person> findByAddressZipCode (string zipCode) List<Person> findByAddress_ZipCode (string zipCode)
声明的查询支持的前缀
关键词
说明
find, get, query, stream
查询所有的字段
count
统计数量
exists
是否存在
delete , remove
移除
save
保存或更新
* 声明的查询支持的关键字, 在类 org.springframework.data.repository.query.parser.PartTree 定义*
关键词
举例
Distinct
findDistinctByLastnameAndFirstname
And
findByLastnameAndFirstname
Or
findByLastnameOrFirstname
Is,Equals
findByFirstname, findByFirstnameIs,findByFirstnameEquals
Between
findByStartDateBetween
LessThan
findByAgeLessThan
LessThanEqual
findByAgeLessThanEqual
GreaterThan
findByAgeGreaterThan
GreaterThanEqual
findByAgeGreaterThanEqual
After
findByStartDateAfter
Before
findByStartDateBefore
IsNull,Null
findByAge(Is)Null
IsNotNull,NotNull
findByAge(Is)NotNull
Like
findByFirstnameLike
NotLike
findByFirstnameNotLike
StartingWith
findByFirstnameStartingWith
EndingWith
findByFirstnameEndingWith
Containing
findByFirstnameContaining
OrderBy
findByAgeOrderByLastnameDesc
Not
findByLastnameNot
In
findByAgeIn(Collection ages)
NotIn
findByAgeNotIn(Collection ages)
True
findByActiveTrue()
False
findByActiveFalse()
IgnoreCase
findByFirstnameIgnoreCase
综上,总结 3 点经验:
方法名的表达式通常是实体属性连接运算符的组合,如 And、or、Between、LessThan、GreaterThan、Like 等属性连接运算表达式,不同的数据库(NoSQL、MySQL)可能产生的效果不一样,如果遇到问题,我们可以打开 SQL 日志观察。
IgnoreCase 可以针对单个属性(如 findByLastnameIgnoreCase(…)),也可以针对查询条件里面所有的实体属性忽略大小写(所有属性必须在 String 情况下,如 findByLastnameAndFirstnameAllIgnoreCase(…))。
OrderBy 可以在某些属性的排序上提供方向(Asc 或 Desc),称为静态排序,也可以通过一个方便的参数 Sort 实现指定字段的动态排序的查询方法(如 repository.findAll(Sort.by(Sort.Direction.ASC, “myField”)))。
通过 @EnableJpaRepositories 注解需要提供 Repositories 包路径,entityManagerFactory 的beanName,transactionManager 的 beanName 此注解会扫描 Repository 及其子接口,并为其生成代理类,我们操作的是代理类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @ComponentScan({"com.hank"}) @EnableTransactionManagement @EnableJpaRepositories( basePackages = {"com.hank.Pesis"}, entityManagerFactoryRef = "myEntityManagerFactory", transactionManagerRef = "jdbcTransactionManager" ) public class Programmer { public static void main (String[] args) throws Exception { var context = new AnnotationConfigApplicationContext (Programmer.class); var repository = context.getBean(MyPersonRepository.class); System.out.println(Proxy.isProxyClass(repository.getClass())); System.out.println(repository.getClass()); System.out.println(repository); var person = repository.findDistinctByName("ok" ); } }
@Query -JPQL注解查询 @Query 属于JPQL 查询,同时也可以指定原生的 sql 查询方式(nativeQuery = true),除过查询其他方法要加 @Modifying 注解 @Modifying(clearAutomatically = true) 作用是在做更新后立即清除当前缓存,并做一次数据库查询 参数的指定方式有:
?1 传入的参数是方法的第一个参数 ,序号参数
:name 传入的参数是方法的参数上加 @Param(“name”) 注解的,命名参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public interface MyPersonRepository extends Repository <Person, Integer> { @Query(" select u.name from Person u where u.id=1 and u.name=?1") public String findByName1 (String name) ; @Query("from Person u where u.id=1 and u.name=:name") public Person findByName2 (@Param("name") String name) ; @Query(value = "select * from Person u where u.id=1 and u.name=:name", nativeQuery = true) public Person findByName3 (@Param("name") String name) ; @Query(value = "from Person u where u.id=1 and u.name=:#{#person.name}") public Person findByName4 (@Param("person") Person person) ; @Modifying @Transactional @Query(value = "update Person u set u.name=?1 where u.id=?2 ") public int updatePerson (String name,int id) ; @Modifying @Transactional @Query(value = "delete from Person u where u.id=?1") public int deletePerson (int id) ; @Modifying @Transactional @Query(value = "insert into Person (name) values(?1) ",nativeQuery = true) public int insetPerson (String name) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 @ComponentScan({"com.hank"}) @EnableJpaRepositories( basePackages = {"com.hank.Pesis"}, entityManagerFactoryRef = "myEntityManagerFactory", transactionManagerRef = "jdbcTransactionManager" ) @EnableTransactionManagement public class Programmer { public static void main (String[] args) throws Exception { var context = new AnnotationConfigApplicationContext (Programmer.class); var repository = context.getBean(MyPersonRepository.class); System.out.println(Proxy.isProxyClass(repository.getClass())); System.out.println(repository.getClass()); System.out.println(repository); System.out.println(repository.findByName1("ok" )); System.out.println(repository.findByName2("ok" )); System.out.println(repository.findByName3("ok" )); System.out.println(repository.findByName4(new Person (){{setName("ok" );}})); System.out.println(repository.updatePerson("ok" ,1 )); System.out.println(repository.deletePerson(1 )); System.out.println(repository.insetPerson("bad" )); } }
SpEL 表达式 在Spring Data JPA 1.4以后,支持在@Query中使用SpEL表达式(简介)来接收变量,这为使用 @Query 提供了更大的便利性
1 2 3 4 @Query(value = "from #{#entity} u where u.id=1 and u.name=:#{#person.name}") public Person findByName4 (@Param("person") Person person) ;
@NamedQuery-命名查询 @NamedQuery注解定义的查询里写的也是 jpql,之相对应的还有@NamedNativeQuery 书写的是原生 sql 这种把查询方法定义在实体的方式用起来不优雅,一般不使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Entity @Getter @Setter @NamedQuery(name = "Person.myNamedQuery",query = "select c from Person c where c.id=?1") public class Person { @Id @Column(name = "id", nullable = false) @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; @Column(name = "name", nullable = true) String name; }
@NamedStoredProcedureQueries , @Procedure 命名查询的存储过程 @NamedStoredProcedureQueries 必须加载一个实体上 @Procedure 加载方法上
QueryByExampleExecutor 简单化的动态查询 QueryByExampleExecutor 接口实现动态查询创建,并且不需要编写包含字段名称的查询,所谓动态查询是查询的字段不一定有几个,也可以传入排序和分页
提供一个实例作为动态查询条件 Query by Example 也有几个限制:
不支持嵌套或分组的属性约束,例如firstname = ?0 or (firstname = ?1 and lastname = ?2)
仅支持字符串的 starts/contains/ends/regex 匹配和其他属性类型的精确匹配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 public interface QueryByExampleExecutor <T> { <S extends T > Optional<S> findOne (Example<S> example) ; <S extends T > Iterable<S> findAll (Example<S> example) ; <S extends T > Iterable<S> findAll (Example<S> example, Sort sort) ; <S extends T > Page<S> findAll (Example<S> example, Pageable pageable) ; <S extends T > long count (Example<S> example) ; <S extends T > boolean exists (Example<S> example) ; <S extends T , R> R findBy (Example<S> example, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction) ; }
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public interface MyPersonRepository extends Repository <Person, Integer>, QueryByExampleExecutor<Person> { @Override List<Person> findAll (Example example) ; } @ComponentScan({"com.hank"}) @EnableJpaRepositories( basePackages = {"com.hank.Pesis"}, entityManagerFactoryRef = "myEntityManagerFactory", transactionManagerRef = "jdbcTransactionManager" ) @EnableTransactionManagement public class Programmer { public static void main (String[] args) throws Exception { var context = new AnnotationConfigApplicationContext (Programmer.class); var repository = context.getBean(MyPersonRepository.class); var person=new Person (); person.setFirstName("ok3" ); var matcher= ExampleMatcher.matching().withIgnorePaths(Person_.ID); matcher= matcher.withMatcher(Person_.FIRST_NAME, ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.STARTING,true )); var list=repository.findAll(Example.of(person,matcher)); list.forEach(p->{ System.out.println(p); }); } }
流畅的动态化 findBy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 @Entity @Getter @Setter @ToString public class Person { @Id @Column(name = "id", nullable = false) @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; @Column(name = "firstName", nullable = true) String firstName; @Column(name = "lastName", nullable = true) String lastName; @Column(name = "personAge", nullable = true) int age; } @ComponentScan({"com.hank"}) @EnableJpaRepositories( basePackages = {"com.hank.Pesis"}, entityManagerFactoryRef = "myEntityManagerFactory", transactionManagerRef = "jdbcTransactionManager" ) @EnableTransactionManagement public class Programmer { public static void main (String[] args) throws Exception { var context = new AnnotationConfigApplicationContext (Programmer.class); var repository = context.getBean(MyPersonRepository.class); var person=new Person (); person.setFirstName("k1" ); person.setLastName("dA1" ); var exampleMatcher= ExampleMatcher.matching(). withIgnorePaths(Person_.ID).withIgnorePaths(Person_.AGE); var sort= Sort.by(Person_.FIRST_NAME).ascending(); var result= repository.findBy(Example.of(person,exampleMatcher),p->p.sortBy(sort).first()); System.out.println(result.isPresent()?result.get():null ); } }
JpaSpecificationExecutor-可组合的规约查询 规约的最大好处就是可组合可复用
hibernate 的使用方式
1 2 3 4 5 6 7 8 CriteriaBuilder cb = session.getCriteriaBuilder();CriteriaQuery<Person> criteriaQuery = cb.createQuery(Person.class); Root<Person> root = criteriaQuery.from(Person.class); criteriaQuery.select(root).where(cb.equal(root.get("firstName" ), "ok1" )); Query<Person> query = session.createQuery(criteriaQuery); List<Person> results = query.getResultList();
Spring Data JPA 采用 Eric Evans 的书“领域驱动设计”中的规范概念,遵循相同的语义并提供 API 以使用 JPA 标准 API 定义此类规范。为了支持规范,您可以使用接口扩展您的存储库接口JpaSpecificationExecutor,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 public interface JpaSpecificationExecutor <T> { Optional<T> findOne (@Nullable Specification<T> spec) ; List<T> findAll (@Nullable Specification<T> spec) ; Page<T> findAll (@Nullable Specification<T> spec, Pageable pageable) ; List<T> findAll (@Nullable Specification<T> spec, Sort sort) ; long count (@Nullable Specification<T> spec) ; }
规约的接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 public interface Specification <T> extends Serializable { long serialVersionUID = 1L ; static <T> Specification<T> not (@Nullable Specification<T> spec) { return spec = = null ? (root, query, builder) -> null : (root, query, builder) -> builder.not(spec.toPredicate(root, query, builder)); } static <T> Specification<T> where (@Nullable Specification<T> spec) { return spec = = null ? (root, query, builder) -> null : spec; } default Specification<T> and (@Nullable Specification<T> other) { return SpecificationComposition.composed(this , other, CriteriaBuilder::and); } default Specification<T> or (@Nullable Specification<T> other) { return SpecificationComposition.composed(this , other, CriteriaBuilder::or); } @Nullable Predicate toPredicate (Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) ; }
测试代码
为了在规约中使用强类型的属性名编程, JPA 提供了 metemodel 的概念在编译时生成代码,为了使用此代码需要把生成的代码转变为源代码
1 2 3 4 5 <dependency > <groupId > org.hibernate</groupId > <artifactId > hibernate-jpamodelgen</artifactId > <version > 5.6.3.Final</version > </dependency >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public interface MyPersonRepository extends Repository <Person, Integer>, JpaSpecificationExecutor<Person> { @Override List<Person> findAll (Specification<Person> spec) ; } @ComponentScan({"com.hank"}) @EnableJpaRepositories( basePackages = {"com.hank.Pesis"}, entityManagerFactoryRef = "myEntityManagerFactory", transactionManagerRef = "jdbcTransactionManager" ) @EnableTransactionManagement public class Programmer { public static void main (String[] args) throws Exception { var context = new AnnotationConfigApplicationContext (Programmer.class); var repository = context.getBean(MyPersonRepository.class); var spec= new Specification <Person>() { @Override public Predicate toPredicate (Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) { var path1= root.get(Person_.firstName); var path2= root.get(Person_.lastName); var predicate1 = criteriaBuilder.equal(path1,"ok1" ); var predicate2 = criteriaBuilder.equal(path2,"ok2" ); var and= criteriaBuilder.and(predicate1,predicate2); var order1=criteriaBuilder.asc(path1); var order2=criteriaBuilder.asc(path2); return query.where(and).orderBy(order1,order2).getRestriction(); } }; var result = repository.findAll(spec); System.out.println(result.size()); } }
QueryDSL - 解决多表查询,动态查询的通用类型安全的查询利器,代替JPQL QueryDSL 有多个模块,可以支持 JPA、Mongodb、Lucene 等,使用它们的 API 是统一的平台,这里我们用到的 module 是 JPA。
不可否认的是 JPA 使用是非常方便的,极简化的配置,只需要使用注解,无需任何 xml 的配置文件,语义简单易懂,但是,以上的一切都建立在单表查询的前提下的,我们可以使用 JPA 默认提供的方法,简单加轻松的完成 CRUD 操作。 但是如果涉及到多表动态查询, JPA 的功能就显得有些捉襟见肘了,虽然我们可以使用注解 @Query ,在这个注解中写 SQL 或者 HQL 都是在拼接字符串,并且拼接后的字符串可读性非常的差,当然 JPA 还为我们提供了 Specification 来做这件事情
QuerydslPredicateExecutor 接口提供的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 public interface QuerydslPredicateExecutor <T> { Optional<T> findOne (Predicate predicate) ; Iterable<T> findAll (Predicate predicate) ; Iterable<T> findAll (Predicate predicate, Sort sort) ; Iterable<T> findAll (Predicate predicate, OrderSpecifier<?>... orders) ; Iterable<T> findAll (OrderSpecifier<?>... orders) ; Page<T> findAll (Predicate predicate, Pageable pageable) ; long count (Predicate predicate) ; boolean exists (Predicate predicate) ; <S extends T , R> R findBy (Predicate predicate, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction) ; }
准备工作
1 2 3 4 5 6 <dependency > <groupId > com.querydsl</groupId > <artifactId > querydsl-jpa</artifactId > <version > 5.0.0</version > </dependency >
此插件需要 querydsl-apt 依赖,此插件会在编译时生成强类型的实体类型,特征是生成在在实体前加Q的强类型元素据,类似于 JPA 的 metamodel,需要把生成的代码加到源码中
如下代码就是强类型的类型变量
1 2 3 QPerson person=QPerson.person; QPerson person= new QPerson ("myPerson" );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <plugin > <groupId > com.mysema.maven</groupId > <artifactId > apt-maven-plugin</artifactId > <version > 1.1.3</version > <dependencies > <dependency > <groupId > com.querydsl</groupId > <artifactId > querydsl-apt</artifactId > <version > 5.0.0</version > </dependency > </dependencies > <executions > <execution > <phase > generate-sources</phase > <goals > <goal > process</goal > </goals > <configuration > <outputDirectory > target/generated-sources/java</outputDirectory > <processor > com.querydsl.apt.jpa.JPAAnnotationProcessor</processor > </configuration > </execution > </executions > </plugin >
仓储继承 QuerydslPredicateExecutor 接口 QuerydslPredicateExecutor 接口是谓词查询,缺点是只能是单表查询,返回的实体的所有字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public interface MyPersonRepository extends Repository <Person, Integer>, QuerydslPredicateExecutor<Person> { @Override List<Person> findAll (Predicate predicate) ; } @ComponentScan({"com.hank"}) @EnableJpaRepositories( basePackages = {"com.hank.Pesis"}, entityManagerFactoryRef = "myEntityManagerFactory", transactionManagerRef = "jdbcTransactionManager" ) @EnableTransactionManagement public class Programmer { public static void main (String[] args) throws Exception { var context = new AnnotationConfigApplicationContext (Programmer.class); var repository = context.getBean(MyPersonRepository.class); var person=QPerson.person; var booleanExpression1=person.firstName.in("k1" , "k2" ); var booleanExpression2=person.lastName.containsIgnoreCase("a1" ); var booleanExpression3=person.id.goe(4 ); var list= repository.findAll(booleanExpression1.and(booleanExpression2).and(booleanExpression3)); System.out.println(list.get(0 ).toString()); } }
仓储注入 JPAQueryFactory 对象链式查询,多表,多字段查询 JPAQueryFactory 用来生成 JPAQuery,当然也可以自己 new JPAQuery
1 2 3 4 5 var myPerson = QPerson.person; JPAQuery<Person> query = new JPAQuery <Person>(entityManager); JPAQuery<Person> query = jPAQueryFactory.selectFrom(myPerson)
配置 JPAQueryFactory 工厂在仓储里使用
1 2 3 4 5 6 7 8 9 10 11 12 @Configuration public class QueryDSLConfig { @PersistenceContext private EntityManager entityManager; @Bean @Scope(BeanDefinition.SCOPE_PROTOTYPE) public JPAQueryFactory getJPAQueryFactory () { return new JPAQueryFactory (entityManager); } }
链式写法的 jpaQuery 用java 语法写SQL 非常的方便
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public interface MyPersonRepository extends Repository <Person, Integer> { default List<Person> findDSL (JPAQueryFactory jPAQueryFactory) { var person=QPerson.person; var jpaQuery=jPAQueryFactory.select(person) .from(person) .where(person.firstName.containsIgnoreCase("ok" )); var list= jpaQuery.fetch(); return list; } } @ComponentScan({"com.hank"}) @EnableJpaRepositories( basePackages = {"com.hank.Pesis"}, entityManagerFactoryRef = "myEntityManagerFactory", transactionManagerRef = "jdbcTransactionManager" ) @EnableTransactionManagement public class Programmer { public static void main (String[] args) throws Exception { var context = new AnnotationConfigApplicationContext (Programmer.class); var repository = context.getBean(MyPersonRepository.class); var jPQLQueryFactory= context.getBean(JPAQueryFactory.class); System.out.println(repository.findDSL(jPQLQueryFactory)); } }
更新删除 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public interface MyPersonRepository extends Repository <Person, Integer> { @Transactional default long updateDSL (JPAQueryFactory jPAQueryFactory) { var person = QPerson.person; var updateClause = jPAQueryFactory.update(person) .set(person.firstName, "name good" ) .where(person.id.eq(1 )); return updateClause.execute(); } @Transactional default long deleteDSL (JPAQueryFactory jPAQueryFactory) { var person = QPerson.person; var deleteClause = jPAQueryFactory.delete(person) .where(person.firstName.eq("K2" )); return deleteClause.execute(); } }
查询部分字段 返回一个字段
1 2 3 4 5 6 7 8 9 10 public interface MyPersonRepository extends Repository <Person, Integer> { default String queryDSL (JPAQueryFactory jPAQueryFactory) { var person = QPerson.person; var jpaQuery = jPAQueryFactory.select(person.lastName) .from(person) .where(person.id.eq(1 )); return jpaQuery.fetchOne(); } }
返回多个字段组合的是元组
1 2 3 4 5 6 7 8 9 10 11 12 13 public interface MyPersonRepository extends Repository <Person, Integer> { default Tuple queryDSL (JPAQueryFactory jPAQueryFactory) { var person = QPerson.person; var jpaQuery = jPAQueryFactory.select(person.lastName,person.lastName) .from(person) .where(person.id.eq(1 )); var tuple=jpaQuery.fetchOne(); System.out.println(tuple.get(person.lastName)); System.out.println(tuple.get(person.lastName)); return tuple; } }
返回多个字段组合的的元组不方便使用,还可以进行投影
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Value public class MyReturnDto { String firstNameDto; String lastNameDto; public String getFullName () { return firstNameDto+"," +lastNameDto; } } public interface MyPersonRepository extends Repository <Person, Integer> { default MyReturnDto queryDSL (JPAQueryFactory jPAQueryFactory) { var person = QPerson.person; var jpaQuery = jPAQueryFactory.select( Projections.constructor(MyReturnDto.class, person.firstName.as("firstNameDto" ),person.lastName.as("lastNameDto" ))) .from(person) .where(person.id.eq(1 )); var myReturnDto=jpaQuery.fetchOne(); System.out.println(myReturnDto.getFullName()); return myReturnDto; } }
关联和子查询 关联查询,支持 内联结、联结、左联结和右联结
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @Entity @Data public class OrderCargo { @Id @Column(name = "id", nullable = false) @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; private int PersonId; private String CargoName; } public interface MyPersonRepository extends Repository <Person, Integer> { default List<Tuple> queryDSL (JPAQueryFactory jPAQueryFactory) { var person = QPerson.person; var orderCargo = QOrderCargo.orderCargo; var jpaQuery= jPAQueryFactory.select(person,orderCargo). from(person).join(orderCargo).on(person.id.eq(orderCargo.PersonId)); var result = jpaQuery.fetch(); result.forEach(p->{ var i= p.get(person); var j=p.get(orderCargo); System.out.println(i); System.out.println(j); }); return result ; } }
子查询,提过静态方法 JPAExpressions 提供子查询
1 2 3 4 5 6 7 8 9 10 @Entity @Data public class CargoType { @Id @Column(name = "id", nullable = false) @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; private String CargoName; }
通过子查询实现 orderCargo 的 CargoName 出现在 cargoType 的 CargoName 里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public interface MyPersonRepository extends Repository <Person, Integer> { default List<OrderCargo> queryDSL (JPAQueryFactory jPAQueryFactory) { var orderCargo = QOrderCargo.orderCargo; var type= QCargoType.cargoType; var jpaQuery= jPAQueryFactory.selectFrom(orderCargo) .where(orderCargo.CargoName.in(JPAExpressions.select(type.CargoName).from(type))); var result= jpaQuery.fetch(); result.forEach(p->{ System.out.println(p); }); return result ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 @Entity @Getter @Setter @ToString public class Person { @Id @Column(name = "id", nullable = false) @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; @Column(name = "firstName", nullable = true) String firstName; @Column(name = "lastName", nullable = true) String lastName; @Column(name = "personAge", nullable = true) int age; } public interface MyPersonRepository extends Repository <Person, Integer> { default List<Person> queryDSL (JPAQueryFactory jPAQueryFactory) { var person = QPerson.person; var jpaQuery= jPAQueryFactory.selectFrom(person) .where(person.age.goe(JPAExpressions.select(person.age.avg()).from(person))); var result=jpaQuery.fetch(); result.forEach(p->{ System.out.println(p); }); return result ; } } public interface MyPersonRepository extends Repository <Person, Integer> { default List<Person> queryDSL (JPAQueryFactory jPAQueryFactory) { var person = QPerson.person; var q=new QPerson ("q" ); var jpaQuery= jPAQueryFactory.selectFrom(person) .where(person.age.goe(JPAExpressions.select(q.age.avg()).from(q))); var result=jpaQuery.fetch(); result.forEach(p->{ System.out.println(p); }); return result ; } }
分组和分页及排序 按照某个属性进行分组,获取也只能获取分组的属性,可以多次分组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public interface MyPersonRepository extends Repository <Person, Integer> { default List<Integer> queryDSL (JPAQueryFactory jPAQueryFactory) { var person = QPerson.person; var list = jPAQueryFactory.select(person.age).from(person).groupBy(person.age).fetch(); list.forEach(p->{ System.out.println(p); }); return list ; } } public interface MyPersonRepository extends Repository <Person, Integer> { default List<Object> queryDSL (JPAQueryFactory jPAQueryFactory) { var person = QPerson.person; var list = jPAQueryFactory.select(person.firstName,person.lastName).from(person).groupBy(person.firstName,person.lastName).fetch(); list.forEach(p->{ System.out.println(p); }); return Collections.singletonList(list); } } public interface MyPersonRepository extends Repository <Person, Integer> { default List<Integer> queryDSL (JPAQueryFactory jPAQueryFactory) { var person = QPerson.person; var list=jPAQueryFactory.select(person.age).from(person) .groupBy(person.age).having(person.age.gt(20 )).fetch(); list.forEach(p->{ System.out.println(p); }); return list; } }
分页 通过 offset 设置页码,limit 设置每页记录数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public interface MyPersonRepository extends Repository <Person, Integer> { default QueryResults<Person> queryDSL (JPAQueryFactory jPAQueryFactory) { var person = QPerson.person; var pageable= PageRequest.of(0 ,2 ); var queryResults=jPAQueryFactory.selectFrom(person) .offset(pageable.getOffset()).limit(pageable.getPageSize()).fetchResults(); System.out.println(queryResults.getTotal()); queryResults.getResults().forEach(p->{ System.out.println(p); }); return queryResults; } }
排序 orderBy
1 2 3 4 5 6 7 8 9 10 11 12 13 public interface MyPersonRepository extends Repository <Person, Integer> { default List<Person> queryDSL (JPAQueryFactory jPAQueryFactory) { var person = QPerson.person; var list=jPAQueryFactory.selectFrom(person) .orderBy(person.age.asc()).orderBy(person.id.asc()).fetch(); return list; } }
聚合函数,本质还是调用数据库的聚合函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public interface MyPersonRepository extends Repository <Person, Integer> { default List<String> queryDSL (JPAQueryFactory jPAQueryFactory) { var person = QPerson.person; var max=jPAQueryFactory.select(person.age.max()).from(person).fetchOne(); System.out.println(max); var min=jPAQueryFactory.select(person.age.min()).from(person).fetchOne(); System.out.println(min); var avg=jPAQueryFactory.select(person.age.avg()).from(person).fetchOne(); System.out.println(avg); var sum=jPAQueryFactory.select(person.age.sum()).from(person).fetchOne(); System.out.println(sum); var list=jPAQueryFactory.select(person.age.subtract(1 )).from(person).fetch(); list.forEach(p->{ System.out.println(p); }); var list1=jPAQueryFactory.select(person.firstName.prepend("hi," )).from(person).fetch(); list1.forEach(p->{ System.out.println(p); }); return list1; } }
spring 扩展的 参数分页和排序-Pageable/Sort Pageable 分页
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public interface MyPersonRepository extends Repository <Person, Integer> { Page<Person> findPersonByName (String name, Pageable pageable) ; } @ComponentScan({"com.hank"}) @EnableJpaRepositories( basePackages = {"com.hank.Pesis"}, entityManagerFactoryRef = "myEntityManagerFactory", transactionManagerRef = "jdbcTransactionManager" ) @EnableTransactionManagement public class Programmer { public static void main (String[] args) throws Exception { var context = new AnnotationConfigApplicationContext (Programmer.class); var repository = context.getBean(MyPersonRepository.class); Pageable pageable= PageRequest.of(0 ,2 ); var pages= repository.findPersonByName("ok" , pageable); System.out.println("可查询的总总页数" +pages.getTotalPages()); System.out.println("可查询的总条目数" +pages.getTotalElements()); System.out.println("本次查询到的集合" +pages.get().collect(Collectors.toList())); } }
Sort 排序
推荐 Sort.TypedSort<> 排序,而不是硬编码的排序
public interface MyPersonRepository extends Repository<Person, Integer> { List findPersonByName(String name, Sort sort); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @ComponentScan({"com.hank"}) @EnableJpaRepositories( basePackages = {"com.hank.Pesis"}, entityManagerFactoryRef = "myEntityManagerFactory", transactionManagerRef = "jdbcTransactionManager" ) @ComponentScan({"com.hank"}) @EnableJpaRepositories( basePackages = {"com.hank.Pesis"}, entityManagerFactoryRef = "myEntityManagerFactory", transactionManagerRef = "jdbcTransactionManager" ) @EnableTransactionManagement public class Programmer { public static void main (String[] args) throws Exception { var context = new AnnotationConfigApplicationContext (Programmer.class); var repository = context.getBean(MyPersonRepository.class); Sort sort=Sort.by("name" ).ascending(); Sort.TypedSort<Person> sortPerson=Sort.sort(Person.class); sortPerson.by(Person::getName).ascending(); var list= repository.findPersonByName("ok" , sortPerson); System.out.println(list); } }
分页和排序一起使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public interface MyPersonRepository extends Repository <Person, Integer> { Page<Person> findPersonByName (String name, Pageable pageable) ; } @ComponentScan({"com.hank"}) @EnableJpaRepositories( basePackages = {"com.hank.Pesis"}, entityManagerFactoryRef = "myEntityManagerFactory", transactionManagerRef = "jdbcTransactionManager" ) @EnableTransactionManagement public class Programmer { public static void main (String[] args) throws Exception { var context = new AnnotationConfigApplicationContext (Programmer.class); var repository = context.getBean(MyPersonRepository.class); Sort.TypedSort<Person> sortPerson=Sort.sort(Person.class); sortPerson.by(Person::getName).ascending(); Pageable pageable= PageRequest.of(0 ,2 ,sortPerson); var pages= repository.findPersonByName("ok" , pageable); System.out.println("可查询的总总页数" +pages.getTotalPages()); System.out.println("可查询的总条目数" +pages.getTotalElements()); System.out.println("本次查询到的集合" +pages.get().collect(Collectors.toList())); } }
查询返回结果 常见的返回结果:
List
Slice
Page
Stream
CompletableFuture
Optional
Stream 流的传输需要数据库的配合,数据库要支持数据的流式传输才行,需要在消费流的方法上加 @Transactional(readOnly = true),流在用完后记得要关闭,这种方式的好处是对数据量大处理比较好
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Component class Bar { private final MyPersonRepository myPersonRepository; public Bar (MyPersonRepository myPersonRepository) { this .myPersonRepository=myPersonRepository; } @Transactional(readOnly = true) public void f () { try (var stream= myPersonRepository.findPersonByName("ok" )) { stream.forEach(p-> System.out.println(p.getId())); } } }
CompletableFuture 可以使用Spring的异步方法执行功能异步的存储库查询。这意味着方法将在调用时立即返回,并且实际的查询执行将发生在已提交给Spring TaskExecutor的任务中,比较适合定时任务的实际场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public interface MyPersonRepository extends Repository <Person, Integer> { CompletableFuture<List<Person>> findPersonByName (String name) ; } @ComponentScan({"com.hank"}) @EnableJpaRepositories( basePackages = {"com.hank.Pesis"}, entityManagerFactoryRef = "myEntityManagerFactory", transactionManagerRef = "jdbcTransactionManager" ) @EnableTransactionManagement public class Programmer { public static void main (String[] args) throws Exception { var context = new AnnotationConfigApplicationContext (Programmer.class); var repository=context.getBean(MyPersonRepository.class); var future=repository.findPersonByName("ok" ); future.thenAcceptAsync(p->{ System.out.println(p.stream().collect(Collectors.toList())); }); System.out.println("结束" ); } }
Page 继承自 Slice ,Slice 不返回分页的可以使用的总页数,关心的是是否有下一页
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public interface MyPersonRepository extends Repository <Person, Integer> { Slice<Person> findPersonByFirstName (String name,Pageable pageable) ; } @ComponentScan({"com.hank"}) @EnableJpaRepositories( basePackages = {"com.hank.Pesis"}, entityManagerFactoryRef = "myEntityManagerFactory", transactionManagerRef = "jdbcTransactionManager" ) @EnableTransactionManagement public class Programmer { public static void main (String[] args) throws Exception { var context = new AnnotationConfigApplicationContext (Programmer.class); var repository = context.getBean(MyPersonRepository.class); var pageable = PageRequest.of(0 , 2 ); var slice = repository.findPersonByFirstName("ok" , pageable); System.out.println(slice.getContent()); if (slice.hasNext()){ System.out.println("还有数据可以加载" ); } } }
使用 Optional 避免空指针
如下代码会报错 ❌
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public interface MyPersonRepository extends Repository <Person, Integer> { Person findTop1ByFirstName (String name) ; } @ComponentScan({"com.hank"}) @EnableJpaRepositories( basePackages = {"com.hank.Pesis"}, entityManagerFactoryRef = "myEntityManagerFactory", transactionManagerRef = "jdbcTransactionManager" ) @EnableTransactionManagement public class Programmer { public static void main (String[] args) throws Exception { var context = new AnnotationConfigApplicationContext (Programmer.class); var repository = context.getBean(MyPersonRepository.class); var person = repository.findTop1ByFirstName("not exist" ); System.out.println(person); } }
正确的方法 Optional ✅
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public interface MyPersonRepository extends Repository <Person, Integer> { Optional<Person> findTop1ByFirstName (String name) ; } @ComponentScan({"com.hank"}) @EnableJpaRepositories( basePackages = {"com.hank.Pesis"}, entityManagerFactoryRef = "myEntityManagerFactory", transactionManagerRef = "jdbcTransactionManager" ) @EnableTransactionManagement public class Programmer { public static void main (String[] args) throws Exception { var context = new AnnotationConfigApplicationContext (Programmer.class); var repository = context.getBean(MyPersonRepository.class); var optionalPerson = repository.findTop1ByFirstName("not exist" ); optionalPerson.ifPresent(p->{ System.out.println(p); }); } }
Projections(投影)对查询结果的扩展 基于接口的投影 使用方法定义查询是不能返回部分属性的,通过 Projections 可以解决 如下代码查询数据库的时候只会查询一个属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @Entity @Getter @Setter public class Person { @Id @Column(name = "id", nullable = false) @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; @Column(name = "firstName", nullable = true) String firstName; @Column(name = "lastName", nullable = true) String lastName; } public interface MyReturn { String getFirstName () ; } public interface MyPersonRepository extends Repository <Person, Integer> { MyReturn findFirstByFirstName (String name) ; } @ComponentScan({"com.hank"}) @EnableJpaRepositories( basePackages = {"com.hank.Pesis"}, entityManagerFactoryRef = "myEntityManagerFactory", transactionManagerRef = "jdbcTransactionManager" ) @EnableTransactionManagement public class Programmer { public static void main (String[] args) throws Exception { var context = new AnnotationConfigApplicationContext (Programmer.class); var repository = context.getBean(MyPersonRepository.class); var result = repository.findFirstByFirstName("ok1" ); System.out.println(result.getFirstName()); } }
如下方法对返回的结果在一个方法里进行投影
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public interface MyReturn { @Value("#{target.firstName+','+target.lastName}") String getFullName () ; } @Component("myBean") public class MyBean { public String getFullName (Person person) { return person.getFirstName()+"," +person.getLastName(); } } public interface MyReturn { @Value("#{@myBean.getFullName(target)}") String getFullName () ; }
基于DTO 类的投影-推荐 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class MyReturnDto { private final String firstName; private final String lastName; public MyReturnDto (String firstName, String lastName) { this .firstName=firstName; this .lastName=lastName; } public String getFullName () { return firstName+"," +lastName; } } @Value public class MyReturnDto { private String firstName; private String lastName; public String getFullName () { return firstName+"," +lastName; } } public interface MyPersonRepository extends Repository <Person, Integer> { MyReturnDto findFirstByFirstName (String name) ; }
jpa-hibernate 实体的继承关系 会使得表与实体之间的关系变得复杂不直观,增加复杂度,不建议使用。
@MappedSuperclass 只能用在类上 基于代码复用和模型分离的思想,在项目开发中使用JPA的@MappedSuperclass注解将实体类的多个属性分别封装到不同的非实体类中,纯粹的继承,和表没关系,对象之间的字段共享 注意:
标注为@MappedSuperclass的类将不是一个完整的实体类,他将不会映射到数据库表,但是他的属性都将映射到其子类的数据库字段中。
标注为@MappedSuperclass的类不能再标注@Entity或@Table注解,也无需实现序列化接口。
此表在数据库不会存在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Setter @Getter @MappedSuperclass public abstract class Audit { @Column(name = "createUser") private String createBy; @Column(name = "editUser") private String editBy; } @Entity @Data public class Bar extends Audit { @Id @Column(name = "id", nullable = false) @GeneratedValue private Long id; }
@SecondaryTable 数据库把主表的部分字段分到子表 -不用,增加复杂度 子表1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Setter @Getter @Entity public class Base1 { @Id private Long id; public String name1; } 子表2 @Setter @Getter @Entity public class Base2 { @Id private Long id; public String name2; }
主表 @SecondaryTable(name =”base1”,pkJoinColumns = @PrimaryKeyJoinColumn(name = “id”)) 指定主表的相关字段数据保存在子表 在数据库里主表自有 id ,name 字段,其他字段都是保存在子表里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Entity @Setter @Getter @SecondaryTable(name ="base1",pkJoinColumns = @PrimaryKeyJoinColumn(name = "id")) @SecondaryTable(name ="base2",pkJoinColumns = @PrimaryKeyJoinColumn(name = "id")) public class Bar { @Id @Column(name = "id", nullable = false) @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @Column(table = "base1") public String name1; @Column(table = "base2") public String name2; }
@Inheritance -不用,增加复杂度
@Inheritance (strategy = InheritanceType.SINGLE_TABLE) 将所有父类和子类集合在一张表,只有父类有 @Table ,其他表的字段集合到父类 ,在父类里加字段进行区分子类
@Inheritance (strategy = InheritanceType.TABLE_PER_CLASS) 每个子类会生成一张单独的表,父类可以查询所有子类的表数据
@Inheritance (strategy = InheritanceType.JOINED) 每个类分别生成一张单独的表,但是每张表只有自己的属性,没有父类的属性,通过外键关联的形式使两张表关联起来
jpa-hibernate @Entity Callbacks-的回调方法 JPA 协议里面规定,可以通过一些注解,为其监听回调事件、指定回调方法
注解
备注
@PrePersist
在新增之前的回调
@PostPersist
在新增到数据库之前的回调
@PreRemove
在数据库删除之前的回调
@PostRemove
在数据库删除成功之后的回调
@PreUpdate
在更新到数据库之前回调
@PostUpdate
在数据库更新之后的回调
@PostLoad
在实体加载之后的回调
语法注意事项,关于上表所述的几个方法有一些需要注意的地方,如下:
回调函数都是和 EntityManager.flush 或 EntityManager.commit 在同一个线程里面执行的,只不过调用方法有先后之分,都是同步调用,所以当任何一个回调方法里面发生异常,都会触发事务进行回滚,而不会触发事务提交。
Callbacks 注解可以放在实体里面,可以放在 super-class 里面,也可以定义在 entity 的 listener 里面,但需要注意的是:放在实体(或者 super-class)里面的方法,签名格式为“void ()”,即没有参数,方法里面操作的是 this 对象自己;放在实体的 EntityListener 里面的方法签名格式为“void (Object)”,也就是方法可以有参数,参数是代表用来接收回调方法的实体。
使上述注解生效的回调方法可以是 public、private、protected、friendly 类型的,但是不能是 static 和 final 类型的方法。
在标注 @super-class 注解的实体里写回调方法 在 @MappedSuperclass 标记的类里,声明的回调会在所有的实现类里起到横向拦截的功能,可以自己实现审计追踪的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 @Setter @Getter @MappedSuperclass public abstract class Audit { @Column(name = "createUser") private String createBy; @Column(name = "editUser") private String editBy; @PrePersist public void prePersist () { this .setCreateBy( this .getCreateBy()+"新增前" ); System.out.println(this +",prePersist" ); } @PostPersist public void postPersist () { this .setCreateBy( this .getCreateBy()+"新增后" ); System.out.println(this +",postPersist" ); } @PreUpdate public void preUpdate () { this .setCreateBy( this .getCreateBy()+"更新前" ); System.out.println(this +",preUpdate" ); } @PostUpdate public void postUpdate () { this .setCreateBy( this .getCreateBy()+"更新后" ); System.out.println(this +",postUpdate" ); } @PreRemove public void preRemove () { System.out.println(this +",preRemove" ); } @PostRemove public void postRemove () { System.out.println(this +",postRemove" ); } @PostLoad public void load () { System.out.println(this +",load" ); } } @Entity @Data public class Bar extends Audit { @Id @Column(name = "id", nullable = false) @GeneratedValue private Long id; @Override public String toString () { return this .getCreateBy()+";" +this .getEditBy(); } } @Entity @Data public class Foo extends Audit { @Id @Column(name = "id", nullable = false) @GeneratedValue private Long id; @Override public String toString () { return this .getCreateBy()+";" +this .getEditBy(); } }
自定义 EntityListener,不建议使用 此类实例化一次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class MyAuditingEntityListener { @PrePersist public void prePersist (Audit obj) { obj.setCreateBy( obj.getCreateBy()+"新增前" ); System.out.println(obj+",prePersist" ); } @PostPersist public void postPersist (Audit obj) { obj.setCreateBy( obj.getCreateBy()+"新增后" ); System.out.println(obj+",postPersist" ); } @PreUpdate public void preUpdate (Audit obj) { obj.setCreateBy( obj.getCreateBy()+"更新前" ); System.out.println(obj+",preUpdate" ); } @PostUpdate public void postUpdate (Audit obj) { obj.setCreateBy( obj.getCreateBy()+"更新后" ); System.out.println(obj+",postUpdate" ); } @PreRemove public void preRemove (Audit obj) { System.out.println(obj+",preRemove" ); } @PostRemove public void postRemove (Audit obj) { System.out.println(obj+",postRemove" ); } @PostLoad public void load (Audit obj) { System.out.println(obj+",load" ); } }
通过 @EntityListeners 和回调类进行绑定
1 2 3 4 5 6 7 8 9 10 11 @Setter @Getter @MappedSuperclass @EntityListeners(MyAuditingEntityListener.class) public abstract class Audit { @Column(name = "createUser") private String createBy; @Column(name = "editUser") private String editBy; }
jpa-hibernate 乐观锁和重试机制 @Version 原理是会在一次查询时获得 version ,做更新后,在更新到数据库前再次到数据库查询最新的 version ,对比这俩个 version 的数值是否相等,如果相等说明在此时间内数据库的此条记录没有更新,本次操作事务可以成功,否则事务执行失败抛出错误
注意点如果是通过 @Query 执行JPQL , version 需要自己控制
1 2 3 4 5 6 7 8 9 10 11 12 13 @Modifying @Query("update Account set name=:name, version=:version+1 where id=:id and version=:version") int updateBarByVersion (@Param("id") int id,@Param("name") String name, @Param("version") int version) ;@Transactional(rollbackFor = Exception.class) public int updateBarService (Bar bar) { int i = Dao.updateBarByVersion(bar.getId(),bar.getName(),bar.getVersion()); if (i==0 ){ throw new ObjectOptimisticLockingFailureException ("更新bar失败" ,new Exception ()); } return i; }
通过JPA 仓库 实现 CRUD , version 字段不要自己控制 ,jpa 会做判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Entity @Data public class Bar { @Id @Column(name = "id", nullable = false) @GeneratedValue private Long id; private String name; @Version @Setter(AccessLevel.PRIVATE) private int version; }
如果因为 version 不一致就让事务失败了,就放弃此次操作,是不合理的(如网络问题),加入重试机制解决
@EnableRetry 让重试起作用 , @Retryable 在需要重试方法或类上加此注解
@Retryable
value:指定发生的异常进行重试
include:和value一样,默认空,当exclude也为空时,所有异常都重试
exclude:指定异常不重试,默认空,当include也为空时,所有异常都重试
maxAttemps:重试次数,默认3
backoff:重试补偿机制,默认没有
@Backoff注解
delay:指定延迟后重试
multiplier:指定延迟的倍数,比如delay=5000l,multiplier=2时,第一次重试为5秒后,第二次为10秒,第三次为20秒
@Recover
当重试到达指定次数时,被注解的方法将被回调,可以在该方法中进行日志处理。需要注意的是发生的异常和入参类型一致时才会回调。
!参考(https://www.baeldung.com/spring-retry,https://github.com/spring-projects/spring-retry )
依赖的包
1 2 3 4 5 6 7 8 9 10 11 12 13 <dependency > <groupId > org.springframework.retry</groupId > <artifactId > spring-retry</artifactId > <version > 2.0.0</version > </dependency > <dependency > <groupId > org.aspectj</groupId > <artifactId > aspectjrt</artifactId > <version > 1.9.9.1</version > </dependency >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public interface MyRepository extends JpaRepository <Bar, Long> {} @Retryable(value = RuntimeException.class, maxAttempts = 3, backoff = @Backoff(value = 1000, multiplier = 1.5)) public abstract class BaseService { @Recover public void fun (RuntimeException exception) { System.out.println("连续重试还是无效:" +exception.getMessage()); } } @Component public class MyService extends BaseService { @Autowired private MyRepository myRepository; public void f () { var result= myRepository.findById(58L ); result.ifPresent(p->{ p.setName(LocalDateTime.now().toString()); myRepository.save(p); }); } }
启动类上加 @EnableRetry 注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @ComponentScan({"com.hank"}) @EnableJpaRepositories( basePackages = {"com.hank.Pesis"}, entityManagerFactoryRef = "myEntityManagerFactory", transactionManagerRef = "jdbcTransactionManager" ) @EnableRetry @EnableTransactionManagement public class Programmer { public static void main (String[] args) throws Exception { var context = new AnnotationConfigApplicationContext (Programmer.class); var myService= context.getBean(MyService.class); myService.f(); } }
jpa-hibernate 提供的注解
注解
说明
@Entity
将此类标记为实体,被标注的实体,将映射到数据库
@Embedded
将此类标记为值对象
@Table
指定此实体的数据的表名,不指定默认用类名
@Id
将此类的字段标记为主键
@IdClass
利用外部类定义联合主键,标记在实体类上
@Column
将此类的字段和数据库的列名进行绑定
@Version
用于支持乐观锁版本控制
@Transient
标注的字段不会在数据库中存在,比如年龄可以自动计算
@Enumerated
如果字段是枚举类型,默认映射到数据库的是 int 类型,如果想要映射字符串 @Enumerated(EnumType.STRING),建议映射字符串
@GeneratedValue
为主键生成策略
@Basic
表示属性是到数据库表的字段的映射。如果实体的字段上没有任何注解,默认即为@Basic
@Temporal
旧的 java.util.Date 不能区分日期和时间,但是数据库默认有 date , time , datetime. 此注解就是指定映射关系
@Lob
将属性映射为数据库支持的大数据对象类型
@Access
用于指定 entity 里面的注解是写在字段上面,还是 get/set 方法上面生效,非必填。在默认不填写的情况下,当实体里面的第一个注解出现在字段上或者 get/set 方法上面,就以第一次出现的方式为准,不能一些写在字段上,一些写在方法上
@Table 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @Target(TYPE) @Retention(RUNTIME) public @interface Table { String name () default "" ; String catalog () default "" ; String schema () default "" ; UniqueConstraint[] uniqueConstraints() default {}; Index[] indexes() default {}; }
@IdClass , @EmbeddedId 联合主键 @Embeddable 与 @EmbeddedId 注解同样可以做到联合主键的效果,并且更方便
作为符合主键类,要满足以下几点要求。
必须实现Serializable接口。
必须有默认的public无参数的构造方法。
必须覆盖equals和hashCode方法。
equals方法用于判断两个对象是否相同,EntityManger通过find方法来查找Entity时是根据equals的返回值来判断的。
hashCode方法返回当前对象的哈希码,用于equals判断
联合主键必须定义在一个类里
1 2 3 4 5 6 7 @Data @NoArgsConstructor @AllArgsConstructor public class CityIdentity implements Serializable { private String name; private String address; }
@IdClass 定义联合主键,缺点是要写字符串的硬编码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Entity @Data @IdClass(CityIdentity.class) public class City { @Id @Column(name="name") private String name; @Id @Column(name="address") private String address; private int peopleAccount; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public interface MyPersonRepository extends CrudRepository <City, CityIdentity> { List<City> findCityByPeopleAccount (int account) ; } @ComponentScan({"com.hank"}) @EnableJpaRepositories( basePackages = {"com.hank.Pesis"}, entityManagerFactoryRef = "myEntityManagerFactory", transactionManagerRef = "jdbcTransactionManager" ) @EnableTransactionManagement public class Programmer { public static void main (String[] args) throws Exception { var context = new AnnotationConfigApplicationContext (Programmer.class); var repository = context.getBean(MyPersonRepository.class); { var city=new City (); city.setName("bj" ); city.setAddress("维度8" ); city.setPeopleAccount(10000 ); repository.save(city); } { var city=new City (); city.setName("sh" ); city.setAddress("维度8" ); city.setPeopleAccount(10000 ); repository.save(city); } { var id=new CityIdentity (); id.setAddress("维度8" ); id.setName("bj" ); var city = repository.findById(id); System.out.println(city); } } }
@EmbeddedId 定义联合主键,优点是不需要要写字符串的硬编码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Data @NoArgsConstructor @AllArgsConstructor @Embeddable public class CityIdentity implements Serializable { private static final long serialVersionUID = -8229477451589838531L ; private String name; private String address; } @Entity @Data public class City { @EmbeddedId private CityIdentity cityIdentity; private int peopleAccount; }
@GeneratedValue 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public enum GenerationType { TABLE, SEQUENCE, IDENTITY, AUTO }
@Column 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 @Target({METHOD, FIELD}) @Retention(RUNTIME) public @interface Column { String name () default "" ; boolean unique () default false ; boolean nullable () default true ; boolean insertable () default true ; boolean updatable () default true ; String columnDefinition () default "" ; String table () default "" ; int length () default 255 ; int precision () default 0 ; int scale () default 0 ; }
@Temporal 1 2 3 4 5 6 7 8 9 public enum TemporalType { DATE, TIME, TIMESTAMP }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Entity @Data @IdClass(CityIdentity.class) public class City { @Id @Column(name="name") private String name; @Id @Column(name="address") private String address; private int peopleAccount; @Temporal(TemporalType.DATE) private Date overDate; private LocalDate createDate; private LocalTime updateTime; private LocalDateTime happenDateTime; }
@Lob @Lob 注解支持以下数据库类型的字段:
Clob( Character Large Ojects) 类型是长字符串类型,java.sql.Clob、Character[]、char[] 和 String 将被映射为 Clob 类型。
Blob( Binary Large Objects) 类型是字节类型,java.sql.Blob、Byte[]、byte[] 和实现了 Serializable 接口的类型将被映射为 Blob 类型。
注意:Clob、 Blob 占用内存空间较大,一般配合 @Basic(fetch=FetchType.LAZY) 注解将其设置为延迟加载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @Data public class EmbObject implements Serializable { private String name; private int account; } @Entity @Data public class BlobObject { @Id @Column(name = "id", nullable = false) private Long id; @Lob private String summary1; @Lob private char [] summary2; @Lob private Character[] summary3; @Lob private byte [] face1; @Lob private Byte[] face2; @Lob private EmbObject embObject; }
@Basic //TODO 对非关联对象的字段使用延迟加载没有效果 某些情况下不需要立即加载某字段的值,可以采用延迟加载
1 2 3 4 5 6 7 8 9 @Target({METHOD, FIELD}) @Retention(RUNTIME) public @interface Basic { FetchType fetch () default EAGER; boolean optional () default true ; }
@Embeddable , @Embedded 值对象 注意点:
@Embeddable 定义没有主键的值对象
@Embedded 把值对象嵌入实体类里面
@AttributeOverrides 如果一个实体类有多个相同的值对象,用此注解去定义不同值对象在数据库的列名 麻烦之处是如果值对象的字段比较多,此注解写起来相当麻烦
定义值对象
1 2 3 4 5 6 @Data @Embeddable public class MyAddress { private String road; private String city; }
嵌入值对象到实体对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Entity @Data public class CargoOrder { @Id private Long id; private String cargoName; @Embedded @AttributeOverrides({ @AttributeOverride(name = "road",column = @Column(name = "sendAddress_road")), @AttributeOverride(name = "city",column = @Column(name = "sendAddress_city")) }) private MyAddress sendAddress; @Embedded @AttributeOverrides({ @AttributeOverride(name = "road",column = @Column(name = "receiveAddress_road")), @AttributeOverride(name = "city",column = @Column(name = "receiveAddress_city")) }) private MyAddress receiveAddress; }
嵌入值对象集合到实体对象
会另外生产一个表用于保存值对象的集合,数据库值对象集合的表和实体表是强关联的表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Entity @Data public class CargoOrder { @Id @GeneratedValue private Long id; private String cargoName; @ElementCollection(fetch = FetchType.EAGER) private List<MyAddress> receiveAddress; }
针对集合也的写一个强类型的转换器
1 2 3 4 5 6 7 8 9 10 @Converter public class MyInstanceJpaConvertJson1 implements JpaConverterJson <List<Contacts>> { @Override public List<Contacts> getInstance () { return new ArrayList <Contacts>() ; } }
值对象的其他解决方法 值对象作为可序列化对象
1 2 3 4 5 @Data public class Contacts implements Serializable { private String name; private String phone; }
序列化为二进制数据,对集合不行
1 2 3 4 5 6 7 8 9 10 11 12 @Entity @Data public class CargoOrder { @Id private Long id; private String cargoName; @Column(name = "sendContacts") private Contacts sendContacts; }
自定义保存方式,这个最为灵活
自定义一个数据库和实体字段转换器,此转换器把对象转换为 json 字符串
定义一个泛型的转换器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public interface JpaConverterJson <T> extends AttributeConverter <T, String> { T getInstance () ; final static ObjectMapper objectMapper = new ObjectMapper (); @Override default public String convertToDatabaseColumn (T meta) { try { return objectMapper.writeValueAsString(meta); } catch (JsonProcessingException ex) { return null ; } } @Override default public T convertToEntityAttribute (String dbData) { try { var obj = objectMapper.readValue(dbData, (Class<T>) getInstance().getClass()); return obj; } catch (IOException ex) { return null ; } } }
强类型的转换器,每个值对象都要定义一个
1 2 3 4 5 6 7 8 public class MyInstanceJpaConvertJson implements JpaConverterJson <Contacts> { @Override public Contacts getInstance () { return new Contacts (); } }
对实体里的值对象字段应用转换器
1 2 3 4 5 6 7 8 9 10 11 12 @Entity @Data public class CargoOrder { @Id private Long id; private String cargoName; @Column(name = "receiveContacts") @Convert(converter = MyInstanceJpaConvertJson.class) private Contacts receiveContacts; }
嵌入值对象集合到实体对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Entity @Data public class CargoOrder { @Id @GeneratedValue private Long id; private String cargoName; @Column(name = "receiveContacts",length = 2000) @Convert(converter = MyInstanceJpaConvertJson1.class) private List<Contacts> receiveContacts; }
针对集合也的写一个强类型的转换器
1 2 3 4 5 6 7 8 9 10 @Converter public class MyInstanceJpaConvertJson1 implements JpaConverterJson <List<Contacts>> { @Override public List<Contacts> getInstance () { return new ArrayList <Contacts>() ; } }
jpa-hibernate 关联关系 spring data jpa 没有提供关联关系的扩展,关联关系注解包括 @JoinColumn、 @OneToOne、 @OneToMany、 @ManyToOne、 @ManyToMany、@JoinTable、 @OrderBy。 注意点: 多对多关联关系的实体需要加 Serializable 接口
@JoinColumn 注解用来定义主键字段和外键字段的对应关系 如果没有 @JoinColumn 注解的话默认通过两个实体的主键进行关联 name
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 @Repeatable(JoinColumns.class) @Target({METHOD, FIELD}) @Retention(RUNTIME) public @interface JoinColumn { String name () default "" ; String referencedColumnName () default "" ; boolean unique () default false ; boolean nullable () default true ; boolean insertable () default true ; boolean updatable () default true ; String columnDefinition () default "" ; String table () default "" ; ForeignKey foreignKey () default @ForeignKey(PROVIDER_DEFAULT) ; }
@OneToOne 实体和实体是1对1关联 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @Target({METHOD, FIELD}) @Retention(RUNTIME) public @interface OneToOne { Class targetEntity () default void .class; CascadeType[] cascade() default {}; FetchType fetch () default EAGER; boolean optional () default true ; String mappedBy () default "" ; boolean orphanRemoval () default false ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @Entity @Data public class CargoOrder { @Id @Column(name = "id", nullable = false) @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToOne(cascade = CascadeType.ALL,fetch = FetchType.LAZY) @JoinColumn(name = "customer_id") private Customer customer; } @Data @Entity public class Customer implements Serializable { private static final long serialVersionUID = 6286816358723854706L ; @Id @Column(name = "id", nullable = false) @GeneratedValue(strategy=GenerationType.IDENTITY) private Long id; private String name; }
1 2 3 4 5 6 7 8 9 10 11 12 13 var myRepository = context.getBean(MyRepository.class);var result= myRepository.findById(1L );result.ifPresent(p->{ System.out.println(p.getName()); System.out.println(p.getCustomer().getName()); });
@OneToMany 实体和实体是1对多关联 @OneToMany 1的一方没有办法维护多的一方的所有外键,但是在多的一方,可以维护1的一方的外键,如果在多的一方维护外键关系用 @ManyToOne 维护,此种情况 hibernate 会生成一张中间表来维护外键之间的关系 数据库中间表的表名是 CargoOrder_Customer 字段是: CargoOrder_Customer,customers_id
1 2 3 4 5 6 7 8 9 10 11 12 13 @Entity @Data public class CargoOrder { @Id @Column(name = "id", nullable = false) @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToMany(cascade = CascadeType.ALL,fetch = FetchType.LAZY) private Set<Customer> customers=new HashSet <Customer>(); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Setter @Getter @Entity public class Customer implements Serializable { private static final long serialVersionUID = 6286816358723854706L ; @Id @Column(name = "id", nullable = false) @GeneratedValue(strategy=GenerationType.IDENTITY) private Long id; private String name; private Integer age; @ManyToOne private CargoOrder cargoOrder; }
解决方法1: 1端维护外键,但是外键字段定义在多方表里,在多方表插入数据后1端会更新多方表的外键字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Entity @Setter @Getter public class CargoOrder { @Id @Column(name = "id", nullable = false) @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "cargoOrderID") private Set<Customer> customers=new HashSet <Customer>(); } @Data @Entity public class Customer implements Serializable { private static final long serialVersionUID = 6286816358723854706L ; @Id @Column(name = "id", nullable = false) @GeneratedValue(strategy=GenerationType.IDENTITY) private Long id; private String name; }
解决方法2: 1端放弃维护外键,有多方自己维护外键,此外键字段在多方表里,外键的插入是在多方新增数据的时候,同时插入的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @Entity @Setter @Getter public class CargoOrder implements Serializable { private static final long serialVersionUID = 458774547746966971L ; @Id @Column(name = "id", nullable = false) @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToMany(cascade = CascadeType.ALL,fetch = FetchType.LAZY,mappedBy = Customer_.CARGO_ORDER) private Set<Customer> customers=new HashSet <Customer>(0 ); } @Entity @Setter @Getter public class Customer implements Serializable { private static final long serialVersionUID = 5227592687670264157L ; @Id @Column(name = "id", nullable = false) @GeneratedValue(strategy=GenerationType.IDENTITY) private Long id; private String name; @ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.LAZY) @JoinColumn(name = "cargoOrderID") private CargoOrder cargoOrder; }
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 @ComponentScan({"com.hank"}) @EnableJpaRepositories( basePackages = {"com.hank.Pesis"}, entityManagerFactoryRef = "myEntityManagerFactory", transactionManagerRef = "jdbcTransactionManager" ) @EnableTransactionManagement public class Programmer { public static void main (String[] args) throws Exception { var context = new AnnotationConfigApplicationContext (Programmer.class); MyRepository myRepository = context.getBean(MyRepository.class); val cargoOrder = new CargoOrder (); cargoOrder.setName("iphone" ); var cus=new HashSet <Customer>(); cus.add(new Customer (){{ setName(LocalDateTime.now().toString()); setCargoOrder(cargoOrder); }}); cargoOrder.setCustomers(cus); myRepository.save(cargoOrder); { var result= myRepository.<CargoOrder>findById(1L ); result.ifPresent(p->{ val customer = new Customer (); customer.setName(LocalDateTime.now().toString()); customer.setCargoOrder(p); p.getCustomers().add(customer); myRepository.save(p); System.out.println(p); }); } } }
如上俩种方式是互斥的不能混着写,还有就是 lombok 的 @ToString @EqualsAndHashCode 和 hibernate 兼容有问题
@OrderBy 对多端的返回进行排序 一般和@OneToMany一起使用,对多端返回的数据排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Entity @Setter @Getter public class CargoOrder implements Serializable { private static final long serialVersionUID = 458774547746966971L ; @Id @Column(name = "id", nullable = false) @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToMany(cascade = CascadeType.ALL,fetch = FetchType.LAZY,mappedBy = Customer_.CARGO_ORDER) @OrderBy(Customer_.NAME+" asc,"+Customer_.ID+" asc") private Set<Customer> customers=new HashSet <Customer>(0 ); }
@JoinTable 自定义中间表,基本不用 @JoinTable 注解用于关联映射,它是在关联的拥有方进行配置。使用 @JoinTable 注解将创建一个连接表,也称为“中间表”。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Entity @Setter @Getter public class CargoOrder implements Serializable { private static final long serialVersionUID = 458774547746966971L ; @Id @Column(name = "id", nullable = false) @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToMany(cascade = CascadeType.ALL,fetch = FetchType.LAZY) @JoinTable(name = "a_b") private Set<Customer> customers=new HashSet <Customer>(0 ); }
@ManyToMany 多对多-必须要有中间表,基本不用 ManyToMany总是使用中间关系连接表来存储关系。如果两个实体都定义了ManyToMany的话,因为单向关系,会生成有2个中间表。所以需要用 mappedBy 改造,使其只存在一个中间表
jpa-hibernate 缓存 在 JPA2.0 中,缓存分为一级缓存和二级缓存(JPA1.0 只支持一级缓存)
在 JPA 中,持久化上下文(EntityManager)就是 JPA 的一级缓存。在该缓存区中,会将查询到的对象缓存到该区域中。
一级缓存 如果在同一个 EntityManager 中,查询相同 OID 的数据,那么只需要发送一条 sql。在事务提交/关闭 EntityManager 之后,一级缓存会清空。所以在不同的 EntityManager(或者上下文) 中使用不同的一级缓存。JPA 中的一级缓存的缓存能力是非常有限的,因为我们不会经常在一个 EntityManager 中查询相同的数据 。
JPA 中的一级缓存也可以使用下面的方法手动清除缓存数据:
detach:清除一级缓存中指定的对象
clear:清除一级缓存中的所有的缓存数据
二级缓存 JPA 中的二级缓存通常是用来提高应用程序性能的,它可以避免访问已经从数据库加载的数据,提高访问未被修改数据对象的速度,JPA 二级缓存是跨越持久化上下文的,是真正意义上的全局应用缓存 如果二级缓存激活,JPA 会先从一级缓存中查找实体,未找到再从二级缓存中查找。当二级缓存有效时,就不能依靠事务来保护并发的数据,而是依靠锁策略,如在确认修改后,需要手工处理乐观锁失败等。
JPA 二级缓存通常用来提高性能,同时,使用二级缓存可能会导致提取到“陈旧”数据,也会出现并发写的问题。所以二级缓存最好是用在经常读取的数据,比较少更新数据的情况,而不应该对重要数据使用二级缓存.