JPA
定义:是 JavaEE 中一组用于持久化数据的 API,它提供了一种标准的 ORM 规范,用于 Java 对象映射到数据库中。
JPA 的开发是为了简化企业级应用程序的开发,降低应用程序与数据库之间的耦合度,并提高应用程序的可维护性和可扩展性。
JPA 定义了一系列注解和接口,用于描述 Java 对象与数据库表之间的映射关系。其中,最重要的是实体类(Entity)和实体管理器(EntityManager)。
注意:JPA 只是一种规范,具体的实现由不同的厂商提供,比如 Hibernate、EclipseLink 等。因此,在使用 JPA 时,需要选择一个合适的实现,并按照其提供的文档进行配置和使用。
Interview & Answer
什么是 JPA?它的作用是什么?
JPA 中的实体类是什么?如何定义实体类?
JPA 提供了哪些 API 接口?分别有什么作用?
JPA 中的实体管理器是什么?如何使用它来操作实体对象?
JPA 中的注解有哪些?它们的作用是什么?
JPA 中的事务管理是如何实现的?如何配置事务管理器?
JPA 中的缓存机制是如何实现的?什么是一级缓存和二级缓存?
JPA 中的 JPQL 是什么?如何使用 JPQL 进行查询操作?
JPA 的实现有哪些?它们之间有什么区别?
JPA 和 Hibernate 之间有什么关系?它们之间有什么区别?
JPA 实现方式及缓存默认状态
JPA规范并没有指定一级和二级缓存的默认状态,这取决于具体的JPA实现和配置。下面是常见的JPA实现的默认缓存状态:
-
Hibernate:Hibernate是一个常用的JPA实现,它的一级缓存(Session级别的缓存)默认是开启的,而二级缓存(应用程序级别的缓存)默认是关闭的。可以通过配置文件或者代码的方式来开启或关闭二级缓存。
-
EclipseLink:EclipseLink是另一个常用的JPA实现,它的一级缓存(EntityManager级别的缓存)默认是开启的,而二级缓存(应用程序级别的缓存)默认是关闭的。可以通过配置文件或者代码的方式来开启或关闭二级缓存。
-
OpenJPA:OpenJPA是另一个流行的JPA实现,它的一级缓存(EntityManager级别的缓存)默认是开启的,而二级缓存(应用程序级别的缓存)默认是关闭的。可以通过配置文件或者代码的方式来开启或关闭二级缓存。
如何理解都默认开一级缓存
有点类似都在一个事务中,这要求可重复读等,所以都要开启
JPA 注解使用 demo
// 查询
@Query(value = "SELECT * FROM user WHERE name = :name", nativeQuery = true)
List<User> findByName(@Param("name") String name);
// 修改
@Modifying
@Query(value = "UPDATE user SET age = :age WHERE id = :id", nativeQuery = true)
void updateAgeById(@Param("id") Long id, @Param("age") Integer age);
// 删除
@Modifying
@Query(value = "DELETE FROM user WHERE id = :id", nativeQuery = true)
void deleteById(@Param("id") Long id);
// list 入参
@Query("SELECT u FROM User u WHERE u.name IN :names")
List<User> findByNames(@Param("names") List<String> names);
// map 入参
// 在这个示例中,我们定义了一个自定义查询方法findByCondition,使用@Query注解指定了JPQL查询语句。查询语句中使用了实体类User和其属性name、age,使用AND关键字指定查找name属性等于condition中的"name"键对应的值,age属性大于等于condition中的"minAge"键对应的值的用户信息
// 使用Map作为查询条件时,需要注意的是,Map中的键必须与JPQL查询语句中的参数名一致,否则会抛出异常。
@Query("SELECT u FROM User u WHERE u.name = :name AND u.age >= :minAge")
List<User> findByCondition(@Param("condition") Map<String, Object> condition);
// 复杂对象入参
// @Query("SELECT u FROM User u WHERE u.name = :#{#query.name} AND u.age >= :#{#query.minAge}")
// List<User> findByCondition(@Param("query") UserQuery query);
//
//public class UserQuery {
// private String name;
// private Integer minAge;
// // 省略getter和setter方法
//}
// 联表查询
// @Query("SELECT o FROM Order o JOIN o.user u WHERE u.name = :name")
// List<Order> findByUserName(@Param("name") String name);
// null 不更新
@Query("UPDATE TP_USER_ACTIVITY_LOG \n" +
"SET IF :name IS NOT NULL THEN name = :name END IF\n" +
",age = :age\n" +
"WHERE LOG_DATE = :logDate")
Integer updateTest(@Param("name") String name, @Param("age") String age, @Param("logDate") Date logDate);
// 关键在于数据库if 语句使用,不同数据库if的用法有所不同,所以,没有通用性
// where 条件 为 null
// 如果为空时显示1=1 代表参数为真,对查询结果不产生作用。
// "WHERE IF (:byname is not null, c.byname LIKE CONCAT('%',:byname,'%') , 1 = 1) and IF (:isMember is not null, c.is_member = :isMember , 1 = 1) and IF (:isBlacklist is not null, c.is_blacklist = :isBlacklist , 1 = 1) and "
// + "IF (:phone is not null, c.phone = :phone , 1 = 1)"
Hibernate
Hibernate 一级缓存
Hibernate 的一级缓存是 Session 级别的缓存,它默认是开启的,并且通常是不建议关闭的。但是,在某些特定的场景下,关闭一级缓存可能是有必要的,例如:
- 当需要强制刷新缓存,以避免缓存数据和数据库数据不一致时,可以考虑关闭一级缓存。
- 当需要避免缓存数据的过期或者内存溢出时,可以考虑关闭一级缓存。
- 当需要测试或者调试缓存相关的问题时,可以考虑关闭一级缓存。
关闭 Hibernate 的一级缓存有以下两种方式:
① 在 Session 中调用 clear() 方法:通过 Session 的 clear() 方法可以清空一级缓存中的所有数据。例如:
Session session = sessionFactory.openSession();
session.beginTransaction();
// 这里执行数据库操作,将数据写入数据库
session.clear(); // 清空一级缓存
// 这里再次执行数据库操作,从数据库中读取最新的数据
② 在 Hibernate 配置文件中设置缓存策略:通过在 Hibernate 配置文件(如 hibernate.cfg.xml)中设置缓存策略可以关闭一级缓存。例如:
<hibernate-configuration>
<session-factory>
<!-- ... -->
<property name="hibernate.cache.use_second_level_cache">false</property>
<property name="hibernate.cache.use_query_cache">false</property>
<!-- ... -->
</session-factory>
</hibernate-configuration>
在上述配置中,将 hibernate.cache.use_second_level_cache 和 hibernate.cache.use_query_cache 都设置为 false,可以关闭一级缓存和查询缓存。
注意:关闭一级缓存可能会导致性能下降和数据不一致等问题,因此在实际应用中需要慎重考虑是否关闭一级缓存。
Hibernate 一级缓存过期
Hibernate 的一级缓存是 Session 级别的缓存,它存储的是 Session 中查询的实体对象。一级缓存的过期时间是根据缓存中的对象状态和 Session 的状态来判断的。一级缓存的过期包括以下几种情况:
-
对象状态为持久化状态:当Session中的实体对象状态为持久化状态时,即该对象已经被保存到数据库中并且与数据库中的数据保持一致,那么该对象会一直保留在一级缓存中,直到Session关闭或者显式地从缓存中清除。
-
对象状态为游离状态:当Session中的实体对象状态为游离状态时,即该对象已经被从Session中分离出来,与数据库中的数据不再保持一致,那么该对象会失效并从一级缓存中移除。
-
对象状态为脱管状态:当Session中的实体对象状态为脱管状态时,即该对象已经被Session关闭或者从Session中分离出来,那么该对象会失效并从一级缓存中移除。
注意:一级缓存中的对象生命周期与 Session 的生命周期相关联。当 Session 关闭时,所有在一级缓存中的对象都会失效并从缓存中移除。因此,在使用 Hibernate 的过程中,需要根据具体的业务需求和场景来管理缓存,以避免数据的不一致和缓存的内存溢出等问题。
分布式 Hibernate 二级缓存一致性
在分布式系统中,由于存在多个应用程序实例和多个数据库实例,使用Hibernate的二级缓存可能会导致数据的不一致性问题。为了保持Hibernate的二级缓存一致性,可以采用以下几种方法:
-
配置缓存同步策略:通过配置缓存同步策略,可以保证多个应用程序实例之间的缓存数据一致性。例如,可以使用JGroups、ZooKeeper等工具来实现缓存同步和协调。
-
配置缓存失效策略:通过配置缓存失效策略,可以及时使缓存数据失效,避免缓存数据和数据库数据不一致。例如,可以使用时间戳、版本号等方式来实现缓存数据的失效和更新。
-
选择合适的二级缓存实现:不同的二级缓存实现具有不同的特点和优缺点,需要根据具体的业务需求和场景来选择合适的缓存实现。例如,Ehcache、Redis等缓存工具都是常用的二级缓存实现,它们都具有不同的特点和适用场景。
-
避免缓存数据过期:在使用Hibernate的二级缓存时,需要注意缓存数据的过期时间,避免缓存数据过期导致数据不一致。需要根据具体的业务需求和场景来设置缓存数据的过期时间,以保证数据的一致性和有效性。
注意:保持 Hibernate 的二级缓存一致性是一个复杂的问题,需要综合考虑多个因素和技术手段。在实际应用中,需要根据具体的业务需求和场景来选择合适的缓存策略和技术,以保证系统的性能和可靠性。
Springboot 接入 Hibernate
- maven 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
- yaml 配置
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
- 创建 Entity
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
private String name;
@Column(name = "age")
private int age;
// getters and setters
}
- 创建 Repository
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByName(String name);
}
- 调用
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public List<User> findByName(String name) {
return userRepository.findByName(name);
}
}