650 likes | 663 Views
Learn about the concept of persistence in application development and how to use Java Persistence API (JPA) for managing relational data in Java applications. Explore the features and specifications of JPA, including entities, persistent fields, primary keys, and identifier generation. Customize entity objects and manage them using the entity manager.
E N D
Высшая школа ИТИС Лекция 4 – Java Persistence API (JPA) 14 октября 2013 Веб-разработка на Java Алина Витальевна Васильева доцент факультета Компьютерных Наук Латвийский Университет инженер-разработчик, Одноклассники, Mail.ru Group alina.vasiljeva@gmail.com
What is persistence? • Object persistence means that individual objects can outlive the process that created it • They can be saved to a data store and be re-created at the later point in time • Persistence is one of the fundamental concepts in application development • Persistence in Java normally means storing data in a relational database using SQL
Layered architecture • A typical, proven, high-level application architecture uses three layers: • A persistence layer is the basis in a layered architecture
Java Database Connectivity Hibernate JDBC ORM implementation Persistence solutions Java Persistence API Java application SQL-based relational database
Java Persistence API The Java Persistence API provides an object/relational mapping facility for managing relational data in Java applications "An ORM implementation is a complex beast – less complex than an application server, but more complex than a web application framework like Struts or Tapestry“ Hibernate in Action
Object-Relational Mapping Java Application Database ORM Java Class Diagram ER Diagram
Java Persistence • Java Persistence consists of three areas: • Java Persistence API • Query language • Object/relational mapping metadata • Specification • JPA 2.1 (22 May 2013, part of Java EE 7) • JSR 328:http://jcp.org/en/jsr/detail?id=338
Entities • An entity is a lightweight persistence domain object • Java class that typically represents a table in a relational database, instances correspond to rows • Requirements: • annotated with the javax.persistence.Entity annotation • public or protected, no-argument constructor • the class must not be declared final • no methods or persistent instance variables must be declared final
Requirements for Entities (cont.) • Entities may extend both entity and non-entity classes • Non-entity classes may extend entity classes • Persistent instance variables must be declared private, protected, or package-private • No required business/callback interfaces • Example: @Entity public class Person { private String fullName; public String getFullName() { return fullName; } public void setFullName(String fullName) { this.fullName = fullName; } }
Persistent Fields and Properties • The persistent state of an entity can be accessed: • through the entity’s instance variables • through JavaBeans-style properties • Supported types: • primitive types, String, other serializable types, enumerated types • other entities and/or collections of entities • All fields not annotated with @Transientor not marked as Java transient will be persisted to the data store!
Primary Keys in Entities Each entity must have a unique object identifier (persistent identifier) • @Entity • public class Employee { • @Id private int id; • private String name; • private Date age; • public int getId() { return id; } • public void setId(int id) { this.id = id; } • . . . • } primary key
Persistent Identity • Identifier (id) in entity = primary key in database • Uniquely identifies entity in memory and in DB • Persistent identity types: • Simple id – single field/property @Id int id; • Compound id – multiple fields/properties @Id int id; @Id String name; • Embedded id – single field of PK class type @EmbeddedId EmployeePK id;
Identifier Generation • Identifiers can be generated in the database by specifying @GeneratedValueon the identifier • Four pre-defined generation strategies: • AUTO, IDENTITY, SEQUENCE, TABLE • Specifying strategy of AUTOindicates that the provider will choose a strategy @Id @GeneratedValue private int id;
Customizing the Entity Object • In most of the cases, the default values of annotation attributes are sufficient • By default the table name corresponds to the unqualified name of the class • Customization: • The defaults of columns can be customized using the@Columnannotation @Entity(name = "FULLTIME_EMPLOYEE") public class Employee{ …… } @Id @Column(name = "EMPLOYEE_ID", nullable = false) private String id; @Column(name = "FULL_NAME" nullable = true, length = 100) private String name;
Managing Entities • Entities are managed by the entity manager • The entity manager is represented by javax.persistence.EntityManager instances • Each EntityManager instance is associated with a persistence context • A persistence context defines the scope under which particular entity instances are created, persisted, and removed
Persistence Context Persistence Context Application EntityManager MyEntity A MyEntity C MyEntity a MyEntity B MyEntity b Entities Entity state
Entity Manager • An EntityManager instance is used to manage the state and life cycle of entities within a persistence context • The EntityManager API: • creates and removes persistent entity instances • finds entities by the entity’s primary key • allows queries to be run on entities • There are two types of EntityManagers: • Application-Managed EntityManagers • Container-Managed EntityManagers
Application-Managed EntityManager Can be created manually in a program code: public class PersistenceProgram { public static void main(String[] args) { EntityManagerFactoryemf = Persistence.createEntityManagerFactory("SomePUnit"); EntityManagerem = emf.createEntityManager(); em.getTransaction().begin(); // Perform finds, execute queries, update entities, etc. em.getTransaction().commit(); em.close(); emf.close(); } }
Container-Managed EntityManagers An EntityManager with a transactional persistence context can be injected by a container (e.g. Spring) @Transactional public class PersistenceHelperImpl implements PersistenceHelper { . . . @PersistenceContext publicvoid setEntityManager(EntityManager em) { this.em = em; } publicvoid save(PersistentEntity object) { if (object.getId() == null) { getEntityManager().persist(object); } else { getEntityManager().merge(object); } } }
Operations on Entity Objects EntityManager API operations: • persist()- Insert the state of an entity into the db • remove()- Delete the entity state from the db • refresh()- Reload the entity state from the db • merge()- Synchronize the state of detached entity with the pc • find()- Execute a simple PK query • createQuery()- Create query instance using dynamic JP QL • createNamedQuery()- Create instance for a predefined query • createNativeQuery()- Create instance for an SQL query • contains()- Determine if entity is managed by pc • flush()- Force synchronization of pc to database
Entity Instance’s Life Cycle • Instances are in one of four states: • New • Managed • Detached • Removed • The state of persistent entities is synchronized to the database when the transaction commits • To force synchronization of the managed entity to the data store, invoke the flush() method
Persistence Units • A persistence unit defines: • a database connection settings • a set of all entity classes that are managed by EntityManager instances in an application • Some set of entities can share one common provider (e.g. TopLink), whereas other set of entities can depend on a different provider (e.g. Hibernate) • Persistence units are defined by the persistence.xml configuration file • should be located in project classpath folder META-INF
HibernatePersistence provider Sample configuration of persistence unit: <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0"> <persistence-unit name="hibernate_mysql" transaction-type="RESOURCE_LOCAL"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/> <property name="hibernate.hbm2ddl.auto" value="update"/> <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/> <property name="hibernate.connection.username" value="root"/> <property name="hibernate.connection.password" value="root"/> <property name="hibernate.connection.url" value="jdbc:mysql://localhost/web"/> <property name="hibernate.show_sql" value="true"/> </properties> </persistence-unit> </persistence>
hibernate.hbm2ddl.auto • Automatically validates or exports schema DDL to the database when the SessionFactory is created. With create-drop, the database schema will be dropped when the SessionFactory is closed explicitly • Possible values: • validate • create • update • create-drop
Java Persistence API in Servlets A simple scenario how to use Java Persistence API from Servlet web application • Add JPA libraries to the project • Create the Persistence Class (@Entity) • Create the Data Access Object (DAO) • Obtain EntityManagerFactory in theServletContextListenerand initialize DAO with it
JPA dependencies (Hibernate provider) Add to pom.xml (Maven configuration file): <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>4.2.5.Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>4.2.5.Final</version> </dependency> <dependency> <groupId>org.hibernate.javax.persistence</groupId> <artifactId>hibernate-jpa-2.1-api</artifactId> <version>1.0.0.Final</version> </dependency>
JPA dependencies (MySQL driver) Add to pom.xml (Maven configuration file): <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.26</version> </dependency> You may also use some other vendor driver if you prefer different database
Creating an Entity @Entity public class Person { @Id @GeneratedValue private Long id; private String fullName; public Long getId() { return id; } public String getFullName() { return fullName; } public void setFullName(String fullName) { this.fullName = fullName; } }
Creating a DAO class public class PersonDAO { private EntityManagerFactory emf; public PersonDAO(EntityManagerFactory emf){ this.emf = emf; } public void save(Person person){ EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); em.persist(person); em.getTransaction().commit(); em.close(); } }
Obtaining EntityManagerFactory • In order to perform database operations, we need to obtain an EntityManagerFactory instance • We will use an application-managed approach • when using any kind of a container (e.g. Spring) more convenient container-managed approach may be used • A convenient approach is to create EntityManagerFactoryonce, encapsulating it in some sort of a singleton wrapper class (PersistenceManager), and then use it for different purposes
PersistenceManager public class PersistenceManager { private EntityManagerFactory emf; private static final PersistenceManager INSTANCE = new PersistenceManager(); public static PersistenceManager getInstance() { return INSTANCE; } private PersistenceManager() {} public EntityManagerFactory getEntityManagerFactory() { if (emf == null) createEntityManagerFactory(); return emf; } protected void createEntityManagerFactory() { this.emf = Persistence.createEntityManagerFactory("p_unit"); } public void closeEntityManagerFactory() { if (emf != null) { emf.close(); emf = null; }}}
Creating EntityManagerFactory @WebListener public class PersistenceContextListener implements ServletContextListener { public void contextInitialized(ServletContextEvent event) { ServletContext context = event.getServletContext(); EntityManagerFactory emf = PersistenceManager.getInstance().getEntityManagerFactory(); PersonDAO personDAO = new PersonDAO(emf); context.setAttribute("personDAO", personDAO); } public void contextDestroyed(ServletContextEvent event) { PersistenceManager.getInstance().closeEntityManagerFactory(); } }
Using DAO in a Servlet @WebServlet("/person") public class PersonServlet extends HttpServlet { public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String fullName = request.getParameter("full_name"); Person person = new Person(); person.setFullName(fullName); PersonDAO dao = (PersonDAO)request .getServletContext().getAttribute("personDAO"); dao.save(p); } }
persistence.xml location • JPA configuration file persistence.xml should be placed inside WAR file in: \<WAR>\WEB-INF\classes\META-INF • When using Maven directory structure, then valid place for persistence.xml is: \<project_root>\src\main\resources\META-INF
JPQL Java Persistence Query Language
Introduction • The Java Persistence API specifies a query language that allowsto define queries over entities and their persistent state • JPQL is an extension of EJB QL • More robust flexible and object-oriented than SQL • The persistence engine parse the query string, transform the JPQL to the native SQL before executing it
Creating Queries • Query instances are obtained using: • EntityManager.createNamedQuery(static query) • EntityManager.createQuery (dynamic query) • Query API: getResultList() – execute query returning multiple results getSingleResult()– execute query returning single result executeUpdate()– execute bulk update or delete setFirstResult()– set the first result to retrieve setMaxResults()– set the maximum number of results to retrieve setParameter()– bind a value to a named or positional parameter setHint()– apply a vendor-specific hint to the query setFlushMode()– apply a flush mode to the query when it gets run
Static (Named) Queries • Defined statically with the help of @NamedQueryannotation together with the entity class • @NamedQuery elements: • name - the name of the query that will be used with the createNamedQuery method • query – query string @NamedQuery(name="findAllCustomers", query="SELECT c FROM Customer") Query findAllQuery = entityManager.createNamedQuery("findAllCustomers"); List customers = findAllQuery.getResultList();
Multiple Named Queries Multiple named queries can be logically defined with the help of @NamedQueries annotation @NamedQueries( { @NamedQuery(name = “Mobile.selectAllQuery” query = “SELECT M FROM MOBILEENTITY”), @NamedQuery(name = “Mobile.deleteAllQuery” query = “DELETE M FROM MOBILEENTITY”) } )
Dynamic Queries • Dynamic queries are queries that are defined directly within an application’s business logic • Worse efficiency and slower query execution, as the persistence engine has to do all the parsing and validation stuffs, along with mapping the JPQL to the SQL at the run-time public List findAll(String entityName){ return entityManager.createQuery( "select e from " + entityName + " e") .getResultList(); }
Named Parameters • Named parameters are parameters in a query that are prefixed with a colon (:) • To bound parameter to an argument use method: • Query.setParameter(String name, Object value) public List findWithName(String name) { return em.createQuery( "SELECT c FROM Customer c WHERE c.name LIKE :custName") .setParameter("custName", name) .getResultList(); }
Positional Parameters • Positional parameters are prefixed with a question mark (?) followed the numeric position of the parameter in the query • To set parameter values use method: • Query.setParameter(integer position, Object value) public List findWithName(String name) { return em.createQuery( “SELECT c FROM Customer c WHERE c.name LIKE ?1”) .setParameter(1, name) .getResultList(); }
Native Queries • Queries may be expressed in native SQL • Support for cases where it is necessary to use the native SQL of the target database in use Query q = em.createNativeQuery( "SELECT o.id, o.quantity, o.item " + "FROM Order o, Item i " + "WHERE (o.item = i.id) AND (i.name = 'widget')", com.acme.Order.class); • @SqlResultSetMapping annotaton is used for more advanced cases
Query Operations – Multiple Results • Query.getResultList() will execute a query and may return a List object containing multiple entity instances • Will return a non-parameterized List object • Can only execute on select statements as opposed to UPDATE or DELETE statements • For a statement other than SELECT run-time IllegalStateException will be thrown Query query = entityManager.createQuery(“SELECT C FROM CUSTOMER”); List<MobileEntity> mobiles =(List<MobileEntity>)query.getResultList();
Query Operations – Single Result • A query that returns a single entity object • If the match wasn’t successful, then EntityNotFoundException is returned • If more than one matches occur during query execution a run-time exception NonUniqueResultException will be thrown Query singleSelectQuery = entityManager.createQuery( “SELECT C FROM CUSTOMER WHERE C.ID = ‘ABC-123’”); CustomercustObj = singleSelectQuery.getSingleResult();
Paging Query Results int maxRecords = 10;int startPosition = 0; String queryString = “SELECT M FROM MOBILEENTITY”; while(true){ Query selectQuery = entityManager.createQuery(queryString); selectQuery.setMaxResults(maxRecords); selectQuery.setFirstResult(startPosition); List<MobileEntity> mobiles = entityManager.getResultList(queryString); if (mobiles.isEmpty()){break;} process(mobiles);// process the mobile entities entityManager.clear();// detach the mobile objects startPosition = startPosition + mobiles.size(); }
Flushing Query Objects • Two modes of flushing query objects • AUTO (default) and COMMIT • AUTO - any changes made to entity objects will be reflected the very next time when a SELECT query is made • COMMIT - the persistence engine may only update all the state of the entities during the database COMMIT
JPQL Statement Language • JPQL statement types: • SELECT, UPDATE, DELETE • Supported clauses: • FROM • WHERE • GROUP_BY • HAVING • ORDER BY • … • Conditional expressions, aggregate functions,…
OO-style vs. SQL-style queries • The main difference is that you query the application model, i.e. the entities, rather than any database tables • Productivity can be increased if OO-style queries, e.g. employeeXYZ.getManager().getAddress() are automatically translated by the ORM engine into correct SQL code, e.g. SELECT t3.* FROM EMP t1, EMP t2, ADDR t3 WHERE t1.EMP_ID = “XYZ” AND t1.MGR_ID = t2.EMP_ID AND t2.ADDR_ID = t3.ADDR_ID • Notice that the two-step object traversal was packed into a single DB query