• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
isuning
博客园    首页    新随笔    联系   管理    订阅  订阅
Spring系列之使用JPA-19

我们使用JPA时也完全可以选择Hibernate作为底层实现,但也可以选择其它的JPA提供方,比如EclipseLink。Spring内置了JPA的集成,并支持选择Hibernate或EclipseLink作为实现。这里我们仍然以主流的Hibernate作为JPA实现为例子,演示JPA的基本用法。

在AppConfig中启用声明式事务管理,创建DataSource:

@Configuration
@ComponentScan
@EnableTransactionManagement
@PropertySource("jdbc.properties")
public class AppConfig {
    @Bean
    DataSource createDataSource() { ... }
}

使用Hibernate时,我们需要创建一个LocalSessionFactoryBean,并让它再自动创建一个SessionFactory。使用JPA也是类似的,我们需要创建一个LocalContainerEntityManagerFactoryBean,并让它再自动创建一个EntityManagerFactory:

@Bean
LocalContainerEntityManagerFactoryBean createEntityManagerFactory(@Autowired DataSource dataSource) {
    var entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
    // 设置DataSource:
    entityManagerFactoryBean.setDataSource(dataSource);
    // 扫描指定的package获取所有entity class:
    entityManagerFactoryBean.setPackagesToScan("com.itranswarp.learnjava.entity");
    // 指定JPA的提供商是Hibernate:
    JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
    entityManagerFactoryBean.setJpaVendorAdapter(vendorAdapter);
    // 设定特定提供商自己的配置:
    var props = new Properties();
    props.setProperty("hibernate.hbm2ddl.auto", "update");
    props.setProperty("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
    props.setProperty("hibernate.show_sql", "true");
    entityManagerFactoryBean.setJpaProperties(props);
    return entityManagerFactoryBean;
}

观察上述代码,除了需要注入DataSource和设定自动扫描的package外,还需要指定JPA的提供商,这里使用Spring提供的一个HibernateJpaVendorAdapter,最后,针对Hibernate自己需要的配置,以Properties的形式注入。

最后,我们还需要实例化一个JpaTransactionManager,以实现声明式事务:

@Bean
PlatformTransactionManager createTxManager(@Autowired EntityManagerFactory entityManagerFactory) {
    return new JpaTransactionManager(entityManagerFactory);
}

这样,我们就完成了JPA的全部初始化工作。有些童鞋可能从网上搜索得知JPA需要persistence.xml配置文件,以及复杂的orm.xml文件。这里我们负责地告诉大家,使用Spring+Hibernate作为JPA实现,无需任何配置文件。

所有Entity Bean的配置和上一节完全相同,全部采用Annotation标注。我们现在只需关心具体的业务类如何通过JPA接口操作数据库。

还是以UserService为例,除了标注@Component和@Transactional外,我们需要注入一个EntityManager,但是不要使用Autowired,而是@PersistenceContext:

@Component
@Transactional
public class UserService {
    @PersistenceContext
    EntityManager em;
}

我们回顾一下JDBC、Hibernate和JPA提供的接口,实际上,它们的关系如下:

JDBCHibernateJPA
DataSourceSessionFactoryEntityManagerFactory
ConnectionSessionEntityManager

SessionFactory和EntityManagerFactory相当于DataSource,Session和EntityManager相当于Connection。每次需要访问数据库的时候,需要获取新的Session和EntityManager,用完后再关闭。

但是,注意到UserService注入的不是EntityManagerFactory,而是EntityManager,并且标注了@PersistenceContext。难道使用JPA可以允许多线程操作同一个EntityManager?

EntityManagerFactory实例是线程安全的,但EntityManager实例不是。

实际上这里注入的并不是真正的EntityManager,而是一个EntityManager的代理类,相当于:

public class EntityManagerProxy implements EntityManager {
    private EntityManagerFactory emf;
}

Spring遇到标注了@PersistenceContext的EntityManager会自动注入代理,该代理会在必要的时候自动打开EntityManager。换句话说,多线程引用的EntityManager虽然是同一个代理类,但该代理类内部针对不同线程会创建不同的EntityManager实例。

简单总结一下,标注了@PersistenceContext的EntityManager可以被多线程安全地共享。

因此,在UserService的每个业务方法里,直接使用EntityManager就很方便。以主键查询为例:

public User getUserById(long id) {
    User user = this.em.find(User.class, id);
    if (user == null) {
        throw new RuntimeException("User not found by id: " + id);
    }
    return user;
}

JPA同样支持Criteria查询,比如我们需要的查询如下:

SELECT * FROM user WHERE email = ?

使用Criteria查询的代码如下:

public User fetchUserByEmail(String email) {
    // CriteriaBuilder:
    var cb = em.getCriteriaBuilder();
    CriteriaQuery<User> q = cb.createQuery(User.class);
    Root<User> r = q.from(User.class);
    q.where(cb.equal(r.get("email"), cb.parameter(String.class, "e")));
    TypedQuery<User> query = em.createQuery(q);
    // 绑定参数:
    query.setParameter("e", email);
    // 执行查询:
    List<User> list = query.getResultList();
    return list.isEmpty() ? null : list.get(0);
}

一个简单的查询用Criteria写出来就像上面那样复杂,太恐怖了,如果条件多加几个,这种写法谁读得懂?

所以,正常人还是建议写JPQL查询,它的语法和HQL基本差不多:

public User getUserByEmail(String email) {
    // JPQL查询:
    TypedQuery<User> query = em.createQuery("SELECT u FROM User u WHERE u.email = :e", User.class);
    query.setParameter("e", email);
    List<User> list = query.getResultList();
    if (list.isEmpty()) {
        throw new RuntimeException("User not found by email.");
    }
    return list.get(0);
}

同样的,JPA也支持NamedQuery,即先给查询起个名字,再按名字创建查询:

public User login(String email, String password) {
    TypedQuery<User> query = em.createNamedQuery("login", User.class);
    query.setParameter("e", email);
    query.setParameter("p", password);
    List<User> list = query.getResultList();
    return list.isEmpty() ? null : list.get(0);
}

NamedQuery通过注解标注在User类上,它的定义和上一节的User类一样:

@NamedQueries(
    @NamedQuery(
        name = "login",
        query = "SELECT u FROM User u WHERE u.email=:e AND u.password=:p"
    )
)
@Entity
public class User {
    ...
}

对数据库进行增删改的操作,可以分别使用persist()、remove()和merge()方法,参数均为Entity Bean本身,使用非常简单,这里不再多述。

posted on 2022-05-01 22:17  吕布辕门  阅读(50)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3