实战:应用对持久数据访问| 从开发角度看应用架构9

一、前言

本文仅代表作者的个人观点;

本文的内容仅限于技术探讨,不能作为指导生产环境的素材;

本文素材是红帽公司产品技术和手册;

本文分为系列文章,将会有多篇,初步预计将会有16篇。

二、Java对持久数据的访问方式

前文已经提到,Java应用对应用数据的访问,最终通过ORM方式实现。

而ORM的实现,通过JPA的标准,底层使用Hibernate等技术。

JPA中的几个重要的API:

JPA的API有主要以下几个:实体(entity)、持久性单元(persistence units)、持久性上下文( persistence context)、Entity Manager。我们先看Entity Manager。

几者之间的关系:

一个entity其实就是一个class,只是定了与数据库表的对应。如上图,class叫大魏,数据库中也有一张表叫大魏(类的名称可以和数据库表名不同,使用@Table指定即可)。

大魏这个类,在被生成对象时,会从数据库表中读数据,然后可能会对数据修改,修改的这些数据,会存到持久性上下文中(运行在内存中),在默写情况下,会被存回数据库表中(例如提交)。

java对数据库表的操作,实际上是使用entity manager调用CRUD完成的。而entity manager之所以能对数据库做操作,是因为其底层调用Hibernate,封装了JDBC。而Hibernate相关定义的静态配置,是存放到persistence units中的。

(默认模式下)entity manager是运行到EJB container中,也就是中间件中的。

EntityManagerFactory

EntityManagerFactory 接口主要用来创建 EntityManager 实例。EM 是一个接口,创建的话要 new 它的实现类,工厂类里有好多静态方法,调运时返回一个 EM

EntityManagerFactory该接口约定了如下4个方法:

  • createEntityManager():用于创建实体管理器对象实例。
  • createEntityManager(Map map):用于创建实体管理器对象实例的重载方法,Map 参数用于提供 EntityManager 的属性。
  • isOpen():检查 EntityManagerFactory 是否处于打开状态。实体管理器工厂创建后一直处于打开状态,除非调用close()方法将其关闭。
  • close():关闭 EntityManagerFactory 。 EntityManagerFactory 关闭后将释放所有资源,isOpen()方法测试将返回 false,其它方法将不能调用,否则将导致IllegalStateException异常。

三、实体类对数据的两种访问方式

实体类与标准POJO类相似,但实体有几个重要的区别,需要由EntityManager进行管理。 要将POJO类转换为实体,请在类头中添加@Entity注释。 另外,应该通过使用getter和setter方法来访问每个实例变量。 最后,类必须至少有一个没有参数的构造函数,尽管类仍然可以有其他构造函数接受参数。

以下是一个实体类的例子:

@Entity

public abstract class Customer {

     @Id
     private int custId;
     private String custName;
     ....
     public Customer(){ } // No argument constructor

     //setter and getter methods
     public String getCustName() {
       return custName;
     }
     public void setCustName(String custName) {
       this.custName = custName;
     }

     ...
} 

实体字段和属性

实体类中的非瞬态数据会持久保存到数据库表中。 JPA提供者既可以将数据库表中的数据加载到实体类中,也可以将实体类中的数据存储到数据库表中。 提供者访问状态的方式称为访问模式。 有两种访问模式:基于字段的访问和基于属性的访问。

基于字段的访问Field-based:

这种方式是:通过注释字段提供基于字段的访问。 实体类中的持久字段必须声明为私有,受保护或包级别访问。 持久字段是以下类型之一:

  • Java primitive types: byte, short, int, long, or char
  • java.lang.String type
  • Java Wrapper classes for primitive types: Byte, Short, Integer, Long, or Character
  • Temporal types: java.util.Date, or java.sql.Date
  • Enumerated types
  • Embeddable classes, other entities, and collections of entities

基于字段的访问的示例如下所示:

@Entity
public class Customer implements Serializable {
     // Note that the fields are annotated
     @Id
     protected int custId;
     protected String custName;
     @Temporal(TemporalType.DATE)
     protected Date registrationDate;     
     @Column(name="address")
     protected Address custAddress;
     .....
}

基于字段的访问提供了额外的灵活性。

基于属性的访问--Property-based Access

为了提供基于属性的访问,getter和setter方法必须在Java实体类中定义。因为只能通过方法访问,可以说基于属性的访问提供了更好的封装。 通过注解getter方法提供基于属性的访问。 getter方法的返回类型决定了属性的类型。 getter方法的返回类型必须与传递给setter方法的参数的类型相同。 getter和setter方法必须是public或protected,并且必须遵循Java bean的命名约定。 基于属性的访问的一个例子如下:

@Entity
public class Customer implements Serializable {
     protected int custId;
     protected String custName;
     protected Date registrationDate;
     protected Address custAddress;
     ....
     //Note the getter methods are annotated     @Id
     public int getCustId(){
        return custId;
     }
     public String getCustName(){
        return custName;
     }
     @Temporal(TemporalType.DATE)
     public Date getRegistrationDate(){
        return registrationDate;
     }
     @Column(name="address")
     public Address getCustAddress(){
        return custAddress;
     }
     ....
     //Setter methods
}

四、实体的四种状态

实体的四种类型:

New State: 使用Java新运算符创建的实体实例处于新状态或瞬态状态。 实体实例不具有持久性标识,并且尚未与持久性上下文相关联。

Managed State:具有持久性标识、并与持久性状态关联的实体实例、处于受管状态或持久状态。 当对管理实体字段中的数据进行更改时,它将与数据库表数据同步。 应用程序调用实体管理器的持久性,查找或合并方法后,实体实例处于受管状态。

Removed State:持久实体可以通过多种方式从数据库表中删除。 当提交事务或调用实体管理器的remove方法时,可以从数据库表中删除一个托管实体实例。 一个实体然后处于移除状态。

Detached State: 实体具有持久性实体标识,但不与持久性上下文相关联。 当实体被序列化或在事务结束时会发生这种情况。 这种状态被称为实体的分离状态。

五、EntityManager接口和关键方法

javax.persistence.EntityManager接口用于与持久性上下文进行交互。 实体实例及其生命周期在持久性上下文中进行管理。

javax.persistence.EntityManager API用于创建新的实体实例,通过主键查找实体实例,通过实体实例进行查询以及删除现有的实体实例。 EntityManager的关键方法是:

persist()方法持久化一个实体并使其得到管理。 persist()方法在数据库表中插入一行。 如果persist操作失败,persist()方法将抛出PersistenceException。

@Stateless
public class CustomerServices {
       ....
       public void saveCustomer(Customer customer) {
           ...
           try{ entityManager.persist(customer);
           }catch(PersistenceException persistenceException){
               // code to handle PersistenceException
           }
       }
}

find()方法通过主键搜索特定类的实体并返回一个托管实体实例。 如果找不到对象,则返回null。

@Stateless
public class CustomerServices {
       ....
       public void getCustomer(Customer customer) {
           ...
           Customer customer;
           try{
               customer = entityManager.find(Customer.class,custId);
               if (customer != null){
                 System.out.print(customer.getCustName());
               } else }
                   System.out.print("Not Found");
               }
           }catch(Exception exception){
               // code to handle PersistenceException
           }
       }
}

contains()方法将一个实例作为参数并检查实例是否在持久化上下文中:

@Stateless
public class CustomerServices {
       ....
       public boolean saveCustomer(Customer customer) {
           ...
           entityManager.persist(customer);
           return entityManager.contains(customer);
       }
}

merge()方法更新现有分离实体的表中的数据。 merge()方法为处于新状态或瞬态状态的实体在数据库表中插入新行。 合并操作之后,实体处于受管理状态。

@Stateless
public class CustomerServices {
       ....
       public void updateCustomer(Customer customer) {
           ...
           Customer customer;
           try{
               customer = entityManager.find(Customer.class,custId);               entityManager.merge(customer);
           }catch(Exception exception){
               // code to handle PersistenceException
           }
       }
}

remove()方法删除一个托管实体。 要删除分离的实体,请调用一个返回受管实例的find()方法,然后调用remove()方法。

@Stateless
public class CustomerServices {
       ....
       public void deleteCustomer(Customer customer) {
           ...
           Customer customer;
           try{
               customer = entityManager.find(Customer.class,custId);               entityManager.remove(customer);
           }catch(Exception exception){
               // code to handle PersistenceException
           }
       }
  }

clear()方法清除持久性上下文。 完成此操作后,所有受管实体都处于分离状态。

     ...
             try{                 entityManager.clear();
             }catch(Exception exception){
                 // code to handle PersistenceException
             }

refresh()方法从数据库表中刷新实体实例的状态。 实体实例中的当前数据被从数据库表中提取的数据覆盖。

     ...
             try{                 entityManager.refresh(customer);
             }catch(Exception exception){
                 // code to handle PersistenceException
             }

persistence.xml文件是一个包含持久性单元的标准配置文件。 每个持久性单元都有一个唯一的名称。

1持久性单元名称是持久性单元的名称。持久性单元的名称用于获取EntityManager。

2事务类型可以是JTA或RESOURCE_LOCAL。事务类型定义了应用程序打算执行什么类型的事务。容器事务使用每个Java EE应用程序服务器中提供的Java事务API(JTA)。在JTA类型的事务中,容器负责创建和跟踪实体管理器。在RESOURCE_LOCAL中,您负责创建和跟踪实体管理器。

3jta-data-source是数据源的名称。每个持久性单元都必须有一个数据库连接。 JPA提供程序在启动时使用JNDI查找服务按名称查找数据源。

4可以在属性元素中设置其他标准或特定于供应商的属性。 hibernate.Dialect属性指定使用哪个数据库。具有更新值的hibernate.hbm2ddl.auto属性会自动更新模式。具有值为true的hibernate.show-sql属性可以将SQL语句记录到控制台。

六、实战:应用对持久数据的访问

通过JBDS导入一个已经存在maven项目:

查看一个包的 com.redhat.training.model java的源码: Person.java

修改源码如下位置:

增加import库并 @Entity注释:

通过以上操作,将一个普通的POJO变成了Entity。

Person实体类必须实现Serializable接口。 导入并实现Serializable接口。

将@Id和@GeneratedValue(strategy = GenerationType.IDENTITY)注释添加到Person类的id属性中,使其成为主键,并将其生成为IDENTITY。

将@Column(name =“name”)注释添加到personName属性,以将其映射到数据库表中的名称字段。 导入所需的库。

在com.redhat.training.services包中打开PersonService类并添加持久性功能以将Person保存到数据库并从数据库中查找人员。

需要EntityManager对象来执行PersonService类中的持久性操作。 添加@PersistenceContext注释以获取EntityManager对象:

使用实体管理器将Person持久化到数据库中,将以下代码添加到公共String hello(String name)方法中,如下所示:

找到使用id的人的名字,将方法getPerson(Long id)添加到PersonService类。 在return语句中,使用实体管理器的find()方法根据id返回Person的name属性。

观察getAllPersons()方法,该方法返回存储在数据库中的所有Person对象:

在com.redhat.training.ui包中打开Hello类。 取消注释getPerson()和getPersons()方法,以添加前端功能以查看存储在数据库中的单个人员姓名和所有姓名。

修改为:

启动EAP:

接下来,构建和部署应用。

接下来,在EAP上部署应用:

部署成功:

通过浏览器访问应用:

输入名字:david wei,点击提交:

点击view all names:

说明姓名已经被insert到数据库表中。

参考文档:

  1. 红帽技术文档
  2. https://blog.csdn.net/qq_24084925/article/details/51890054

魏新宇

  • 红帽资深解决方案架构师
  • 专注开源云计算、容器及自动化运维在金融行业的推广
  • 拥有MBA、ITIL V3、Cobit5、C-STAR(云安全评估师)、TOGAF9.1(鉴定级)等管理认证。
  • 拥有红帽RHCE/RHCA、VMware VCP-DCV、VCP-DT、VCP-Network、VCP-Cloud、AIX、HPUX等技术认证。

原文发布于微信公众号 - 大魏分享(david-share)

原文发表时间:2018-06-30

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java后端技术栈

Java多线程编程-(13)-从volatile和synchronized的底层实现原理看Java虚拟机对锁优化所做的努力

对于Java来说我们知道,Java代码首先会编译成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上进行执行。

961
来自专栏chenssy

【死磕Java并发】-----深入分析synchronized的实现原理

记得刚刚开始学习Java的时候,一遇到多线程情况就是synchronized,相对于当时的我们来说synchronized是这么的神奇而又强大,那个时候我们赋予...

1543
来自专栏王磊的博客

Java核心(三)并发中的线程同步与锁

乐观锁、悲观锁、公平锁、自旋锁、偏向锁、轻量级锁、重量级锁、锁膨胀...难理解?不存的!来,话不多说,带你飙车。

1312
来自专栏猛牛哥的博客

快手(AAU)更新记录v2.9.1.23

1967
来自专栏Java帮帮-微信公众号-技术文章全总结

SpringMVC框架复习大纲【面试+提高】

2424
来自专栏Kirito的技术分享

Spring Boot Dubbo应用启停源码分析

Dubbo Spring Boot 工程致力于简化 Dubbo RPC 框架在Spring Boot应用场景的开发。同时也整合了 Spring Boot 特性:

1982
来自专栏程序猿DD

死磕Java并发:深入分析synchronized的实现原理

记得刚刚开始学习Java的时候,一遇到多线程情况就是synchronized。对于当时的我们来说,synchronized是如此的神奇且强大。我们赋予它一个名字...

1437
来自专栏Golang语言社区

Golang语言社区--LollipopGO开源项目搭建商城路由分发

大家好,我是Golang社区主编彬哥,还是要继续社区的开源项目LollipopGO轻量级web框架实战商城。

70320
来自专栏菩提树下的杨过

oracle odp.net 32位/64位版本的问题

如果你的机器上安装了odp.net,且确信machine.config也有类似以下结节:(64位+.net 4.0环境下,machine.config可能会有4...

2926
来自专栏Seebug漏洞平台

Spring MVC 目录穿越漏洞(CVE-2018-1271)分析

2018年04月05日,Pivotal公布了Spring MVC存在一个目录穿越漏洞(CVE-2018-1271)。Spring Framework版本5.0到...

4502

扫码关注云+社区

领取腾讯云代金券