JPA Java Persistence API

JPA (Java Persistence Architecture) is een Java standaard voor object-relational mapping.
Er zijn verschillende Java implementaties waarvan de bekendste Hibernate. In TomEE wordt OpenJPA default toegepast.

Entities

Met JPA worden voor de verschillende database tabellen overeenkomende Java classes gebruikt, Entities genaamd. Deze Entities moeten voldoen aan een aantal uitgangspunten en worden grotendeels geconfigureerd met annotaties.
Met JPA kunnen de tabellen worden aangemaakt in de database o.b.v. de Entity Java classes of andersom kunnen de Entity Java classes worden aangemaakt vanuit de tabellen in de database.
Dit kan worden gedaan met de JPA Tools van Dali, een subproject van het Eclipse Web Tools Platform (WTP) project. Deze worden aan een project toegevoegd door het JPA project Facet te configureren. Ook is het mogelijk in om tabellen te genereren door het configureren van de persistence.xml file met de volgende property:

<property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)"/>

Dit wordt vooral toegepast in testomgevingen waar bijv. gebruik wordt gemaakt van in memory testdatabases.

Entities kunnen in Eclipse met een wizard worden aangemaakt.
De entities worden met het bestand persistence.xml toegekend aan een Persistence Unit. Deze wordt weer gebruikt in de classes die gebruik gaan maken van de entities.

Als de database tables en JPA Entities zijn gemaakt, dan kan hiervan gebruik worden gemaakt door andere classes.

Annotaties

De Entities zijn in eerste instantie een JavaBean met getters en setter. De mapping tussen Entities en tables wordt geconfigureerd met annotaties :

@Entity of @Entity(name=”Entity1”)
Definieert de class als een JPA Entity. Default zijn table en column namen gelijk aan class en property namen. Als een name wordt gespecificeerd, dan wordt in Java de Entity met deze naam gerefereerd.

@Table(name=”Table1″)
Mapt de class op de table met naam “Table1″

@Column(name=”Column1”)
Mapt een class field op de table column met naam “Column1″

@Id
Definieert een class field als een Key.

@GeneratedValue(strategy=GenerationType.IDENTITY)
Geeft aan dat key generatie (automatic index) wordt gebruikt voor een Key, en welk type.
Dit is implementatie specifiek en heeft 4 mogelijkheden : auto, identity, table en sequence
MySQL support identity en Oracle Sequence. Table kan voor elke database en maakt gebruik van een ondersteunende tabel.

@OneToMany(mappedBy=”foreignkeyfield”)
Mapt een Collection type class field van de parent class op een relatie met een child class.
mappedBy specificeert de naam van het foreignkey field in de gerefereerde child class.

@ManyToOne
@JoinColumn(name=”primarykeyfield”)
Mapt een foreign key class field van de child class op een relatie met de parent class.

@Transient
Specificeert aan property als niet persistent. Wordt dus niet opgeslagen in de database

@Lob
Definieert een field als Large Object. Bijv. Strings hebben dat nodig.

@Temporal(TemporalType.DATE)
Definieert het datum/tijd type in de database: DATE, TIME of TIMESTAMP.

@NotNull
Definieert een constraint voor een field als verplicht.

@Pattern(regexp=”….”, message=””)
Definieert een constraint met een reguliere expressie en eventueel foutboodschap.
De foutboodschap kan een letterlijke tekst zijn of middels {} een referentie naar een resource bundle met teksten.

@Past
Definieert een constraint voor field met datum type wat aangeeft dat datum in het verleden moet liggen.

@NamedQueries, @NamedQuery
Definieert named queries voor Entity

Voorbeeld Department Entity Class:

@Entity
public class Department implements Serializable {
	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private int departmentId;

	@Lob
	private String address;

	private int budget;

	@Lob
	private String name;

	//bi-directional many-to-one association to Employee
	@OneToMany(mappedBy="department")
	private List employees;

	public Department() {
	}

	public int getDepartmentId() {
		return this.departmentId;
	}

	public void setDepartmentId(int departmentId) {
		this.departmentId = departmentId;
	}

	public List getEmployees() {
		return this.employees;
	}

	public void setEmployees(List employees) {
		this.employees = employees;
	}

	…..ETC. voor de overige fields……
}

Voorbeeld Employee Entity Class:

@Entity
public class Employee implements Serializable {
	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private int employeeId;

	@Temporal(TemporalType.DATE)
	private Date birthDate;

	@Lob
	private String firstName;

	@Lob
	private String lastName;

	@ManyToOne
	@JoinColumn(name="DepartmentId")
	private Department department;

	public Employee() {
	}

	public int getEmployeeId() {
		return this.employeeId;
	}

	public void setEmployeeId(int employeeId) {
		this.employeeId = employeeId;
	}

	public Date getBirthDate() {
		return this.birthDate;
	}

	public void setBirthDate(Date birthDate) {
		this.birthDate = birthDate;
	}

	public Department getDepartment() {
		return this.department;
	}

	public void setDepartment(Department department) {
		this.department = department;
	}

	…..ETC. voor de overige fields……
}

Benaderen JPA in een EJB Container

Classes die JPA Entities benaderen in een Application EJB Container definieren een EntityManager als class field middels de @PersistenceContext annotatie:

    @PersistenceContext
    EntityManager em;

Hiervan wordt als volgt gebruik gemaak in Create / Update / Delete en Query constructies m.b.v. de EntityManager variabele:

CREATE:

Department dpt = new Department();
dpt.setAddress(“Hoofdstraat 12, AmsterdamStraat”);
dpt.setBudget(100);
dpt.setName(“Verkoop”);
try {
  em.persist(dpt);  // CREATE
}
catch (java.lang.Exception e)
{
}

UPDATE:

Department dpt = em.find(Department.class, 12);
if (dpt != null) {
  dpt.setAddress(“Hoofdstraat 12, AmsterdamStraat”);
  dpt.setBudget(100);
  dpt.setName(“Inkoop”);
  try {
    em.merge(dpt);    // UPDATE
  }
  catch (java.lang.Exception e)
  {
  }
}

DELETE:

try {
   Department dpt = em.find(Department.class, 12);
   if (dpt != null) em.remove(em.merge(dpt));
} catch (IllegalStateException e) {
} catch (SecurityException e) {
}

QUERY:

String name = “Jan”;
List departments = em.createQuery(
"SELECT d FROM Department d WHERE d.depName LIKE :depName")
.setParameter(“depName”, name);
.setMaxResults(10);
.getResultList();

Bovenstaand construct gebruikt de EntityManager variable em om een query op te stellen en uit te laten voeren. De query wordt opgesteld in een soort SQL, genaamd JPQL.
Behalve JPQL kan als alternatief ook worden gewerkt met zgn. Criteria API. Meer een Java api om queries uit te voeren. Deze performen beter.

De EntityManager kent verschillende nuttige methods:

  • find() : Zoek een entity o.b.v. sleutelwaarde
  • remove() : Verwijder een gevonden entity
  • persist() : Sla een entity op
  • merge() : Update een gevonden entity
  • createQuery() : Maak een query o.b.v. een tekst.
  • createNamedQuery() : Maak een query o.b.v. een in een entity voorgedefinieerde query

Met EntityManager worden queries meegegeven via de createQuery() of createNamedQuery() method. Deze methods retourneren een Query object met verschillende methods om de query voor te bereiden en uit te voeren.:

  • setParameter(Parameter, T) : definieert een parameter input
  • setFirstResult(int arg0) : zet de index van het eerste resultaat (nuttig bij paging)
  • setMaxResults(int arg0) : zet het max aantal resultaten (nuttig bij paging)
  • getResultList() : List : voer de query uit en geef de resultaten in een List.
  • getFirstResult() : voer de query uit en geef index van het eerste resultaat.
  • getSingleResult() : Object : voer de query uit en geef één resultaat object.
  • executeUpdate() : voer een UPDATE of DELETE query uit. Deze geven geen resultaat.

Named queries worden bij de Entity class gedefinieerd met annotaties :

@Entity
@NamedQueries({
    @NamedQuery(name = "employee.list",
  query = "select e from Employee e WHERE e.name = :empName")
})

public class Employee implements Serializable {
}

De named query wordt met de createNamedQuery() method vervolgens aangeroepen:

String name = “Inkoop”;
List employees = em.createNamedQuery(“employee.list”)
.setParameter(“empName”, name)
.setMaxResults(10)
.getResultList();

Benaderen JPA buiten een EJB Container

In dit geval moet de applicatie zelf een EntityManager maken middels het EntityManagerFactory object. En Transacties worden gedemarkeerd met het EntityTransaction object.
Dit wordt nogal eens gebruikt in test omgevingen bij JUnit TestCases.
In onderstaand voorbeeld zien we dat de EntityManagerFactory wordt gemaakt voor Persistence Unit “MyUnit” en middels properties wordt voorzien van de driver gegevens.

public class DepartmentTest  {
  private static EntityManagerFactory eFactory;
  private static EntityManager em;
  private static EntityTransaction eTransaction ;

  @BeforeClass
  public static void start() throws Exception {
    Properties properties = new Properties();
    properties.setProperty("openjpa.ConnectionDriverName", "com.mysql.jdbc.Driver");
    properties.setProperty("openjpa.ConnectionURL","jdbc:mysql://localhost:3306/employeedb");
    properties.setProperty("openjpa.ConnectionUserName", "myusername");
    properties.setProperty("openjpa.ConnectionPassword", "mypassword");

    eFactory = Persistence.createEntityManagerFactory("MyUnit",properties);
    em = eFactory.createEntityManager();
    tr = em.getTransaction();
  }

  @AfterClass
  public static void close() throws NamingException {
    em.close();
    eFactory.close();
  }

  @Test
  public void test() throws Exception {
    private static List departments = new ArrayList();
    try {
      tr.begin();
      departments = em.createQuery("SELECT x FROM Department x").getResultList();
      tr.commit();

      assertTrue(!departments.isEmpty());
      assertFalse(departments.isEmpty());
    } catch (Exception ex) {
            fail("Exception during database access.");
    }
  }

JPA QUERIES MET JPQL

Algemene structuur is vergelijkbaar met SQL:


SELECT ... FROM ...
[WHERE ...]
[GROUP BY ... [HAVING ...]]
[ORDER BY ...]

DELETE FROM … [WHERE ...]

UPDATE … SET … [WHERE ...]

SELECT

Minimale query :


SELECT e FROM Employee AS e

Of


SELECT e FROM Employee e

Query wordt als volgt binnen Java toegepast met de EntityManager variabele em:

TypedQuery<Employee > query;
query = em.createQuery("SELECT e FROM Employee e", Employee.class);
List results = query.getResultList();

Of nog korter:

List results = em.createQuery("SELECT e FROM Employee e").getResultList();

Het resultaat van de query uitvoering op deze wijze is een Managed Entity Object of een lijst daarvan. Dat hoeft echter niet. De Select kan ook een pad specificeren naar een field. In dat geval wordt het resultaat afgebeeld naar andere types dan de Entity Classes, bijv. in onderstaand voorbeeld naar een List met Strings:

SELECT c.name FROM Country AS c", String.class);
List results = query.getResultList();

List results = em.createQuery("SELECT e.lastName FROM Employee e", String.class).getResultList();

Een genest pad middels een foreign key is ook mogelijk :

SELECT e.department.name FROM Employee e WHERE……

De Select kan ook meerdere elementen specificeren als volgt, met het resultaat in een List met object arrays :

List results = em.createQuery(
      	"SELECT e.lastName, e.department.name FROM Employee AS e", Object[].class)
.getResultList();

for (Object[] result : results) {
      System.out.println("Employee: " + result[0] + ", Department: " + result[1]);
}

Gebruik van DISTINCT :

SELECT DISTINCT d.name FROM Department AS d WHERE d.name LIKE 'I%'

JOINS

De SQL :

SELECT Department.name, Employee.lastName
FROM Department INNER JOIN Employee ON Department.departmentId=Employees.departmentId
ORDER BY Department.name

Wordt in JPQL:

SELECT d, e FROM Department d INNER JOIN d.employees e

Of

SELECT d, e FROM Department d JOIN d.employees e

String qry = “SELECT d,e FROM Department d INNER JOIN d.employees e”;
List results  = em.createQuery(qry).getResultList();

for (Object[] result : results2) {
System.out.println("Employee = " + ((Employee)result[1]).getLastName() + " /
 Department = " + ((Department)result[0]).getName() );
}

(Het woord INNER mag ook weggelaten)

LEFT JOINS

SELECT d,e FROM Department d INNER LEFT JOIN d.employees e

Deze query levert ook de departments zonder employees, i.t.t. de gewone join.

FETCH

SELECT e FROM Employee e FETCH JOIN e.department

FETCH zorgt ervoor dat de gerelateerde Entity (department in dit geval) ook wordt opgehaald.
Als je dat niet doet en wel in Java de gerelateerde Entity benadert (wat wel kan) dan wordt de database benadering ineffiecient. Want voor elke referentie in een loop wordt de database per loop iteratie benadert.

IN

SELECT x FROM Magazine x, IN(x.articles) y WHERE y.authorName = ‘John Doe’

Haalt alle Magazines met (1 of meer) articles waarvan de article.authorName = ‘John Doe’.

DISTINCT

Elimineert identieke resultaten. Kan als keyword in de SELECT, maar ook in WHERE voor collection type variabelen en paramaters.

Voorbeeld triple join:

We hebben de volgende relaties:

User —< UserRole >—-Role

De volgende SQL selecteert uit UserRole een bepaalde User / Role combinatie om te controleren of een bepaalde Role aan een bepaalde User is toegekend:

SELECT *
FROM UserRole AS ur
JOIN User AS u ON ur.userId = u.userId
JOIN Role AS r  ON ur.roleId = r.roleId
WHERE
u.userName = 'admin' AND
r.name = 'CalcUser';

In JPQL wordt dit:

SELECT ur
FROM UserRole ur
JOIN FETCH ur.user
JOIN FETCH ur.role
WHERE
ur.user.userName = 'admin' AND
ur.role.name = 'Role1'

Hieronder het voorbeeld hoe dit in Java wordt uitgeschreven:


       String qry = 	"SELECT ur FROM UserRole ur " +
        				"JOIN FETCH ur.user " +
        				"JOIN FETCH ur.role " +
        				"WHERE " +
        				"ur.user.userName = '" + userName + "' AND " +
        				"ur.role.name = '" + roleName + "'";

 

	System.out.println("query = " + qry);

       List userroles = em.createQuery(qry, UserRole.class).getResultList();

       System.out.println("Query uitgevoerd");

       for (UserRole ur : userroles) {
        	String foundRole = ur.getRole().getName();
        	System.out.println(	" User = " + ur.getUser().getUserName() +
        				" Role = " + foundRole +
        				" UserRoleId = " + ur.getUserRoleId());
        	if (foundRole.equalsIgnoreCase(roleName)) {
        		rolefound = true;
        	}
        }

OPERATORS

OR, AND, NOT, = , >, >=,, BETWEEN, IN

SELECT x FROM Magazine x WHERE x.title = ‘JDJ’ OR x.title = ‘JavaPro’
SELECT x FROM Magazine x WHERE x.price > 3.00 AND x.price 3.00 AND (x.price= 5.00

MOD() : de restwaarde na een deling. Bijv MOD(11,3) levert 2 op
SELECT x FROM Magazine x WHERE MOD(x.price, 10) = 0

SQRT() : de wortel
SELECT x FROM Magazine x WHERE SQRT(x.price) >= 1.00

AGGREGATE FUNCTIONS

SUM() : Berekent de totale som.

SELECT SUM(d.budget) FROM Department d WHERE d.address LIKE ‘%AMSTERDAM’

COUNT() : Long : geeft aantal objecten van query.

Kan (als enige functie) op zowel de gehele entity als fields worden uitgevoerd.

Voorbeelden:
SELECT COUNT(e) FROM Employee e
SELECT COUNT(e.birthDate) FROM Employee e

Het 2e voorbeeld telt alleen de employees waarvan bithDay is ingevuld (dus niet null).

AVG() : Double : berekent het gemiddelde

MAX() : berekent maximale waarde

MIN() : berekent minimale waarde

STRING FUNCTIONS

CONCAT(string1, string2) : samenvoegen van 2 strings
SELECT x FROM Magazine x WHERE CONCAT(x.title, ‘s’) = ‘JDJs’

SUBSTRING(string, startindex, length) : substring maken van een string
SELECT x FROM Magazine x WHERE SUBSTRING(x.title, 1, 1) = ‘J’

TRIM([LEADING | TRAILING | BOTH] [char FROM] string) : leading en/of trailing characters verwijderen. Als geen character wordt gespecificeerd worden spaties verwijderd.
SELECT x FROM Magazine x WHERE TRIM(BOTH ‘J’ FROM x.title) = ‘D’

LOWER(string) : alles naar kleine letters
SELECT x FROM Magazine x WHERE LOWER(x.title) = ‘jdj’

UPPER(string) : alles naar hoofdletters
SELECT x FROM Magazine x WHERE UPPER(x.title) = ‘JAVAPRO’

LENGTH(string) : de lengte van een string
SELECT x FROM Magazine x WHERE LENGTH(x.title) = 3

LOCATE(searchString, candidateString[,startIndex]) : zoek positie van substring
SELECT x FROM Magazine x WHERE LOCATE(‘D’, x.title) = 2

GROUP BY / HAVING

GROUP BY wordt gebruikt om resultaten te groeperen op een field waarde.
Dit wordt met name gebruikt voor berekeningen op deze groeperingen met Aggregate Functions.

Onderstaand voorbeeld groepeert per eerste letter van de Department.name en berekent daarvan het gemiddelde budget:

SELECT SUBSTRING(d.name,1,1) , AVG(d.budget) FROM Department d
GROUP BY SUBSTRING(d.name,1,1)

HAVING wordt gebruikt om voor een GROUP BY criteria toe te voegen.

ORDER BY

SELECT x FROM Magazine x order by x.title asc, x.price desc

DELETE BY QUERY

DELETE FROM Employee s WHERE ……

Als query method wordt hiervoor executeUpdate() gebruikt i.p.v. getResultList() of getSingleResult().

Voorbeeld:
Query q = em.createQuery(“DELETE FROM Subscription s WHERE s.subscriptionDate < :today”);
q.setParameter(“today”, new Date());
int deleted = q.executeUpdate();

UPDATE BY QUERY

UPDATE Employee e SET e.firstName = :firstname WHERE …..

Als query method wordt hiervoor executeUpdate() gebruikt i.p.v. getResultList() of getSingleResult().

Query q = em.createQuery(“UPDATE Subscription s SET s.paid = :paid WHERE s.subscriptionDate < :today”);
q.setParameter(“today”, new Date());
q.setParameter(“paid”, true);
int updated = q.executeUpdate();

This entry was posted in JEE and tagged . Bookmark the permalink.

Geef een reactie

Je e-mailadres wordt niet gepubliceerd.

De volgende HTML tags en attributen zijn toegestaan: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>