Rise in <b>Chinese</b>-funded acquisitions could trigger more hurdles http://t.co/0pXBS1HR
7 minutes ago by GeorgeHAllenGA on twitter
ZDNet is available in the following editions:
In the second of our series on Java persistence, we look at providing bidirectional relationships between objects.
In the second of our series on Java persistence, we look at providing bidirectional relationships between objects.
In the previous part of this tutorial, we covered the basics of persisting an object using EJB3 persistence, or as it is becoming known Java Persistence API (JPA).
We have simple Person and Address classes being persisted using Hibernate's EntityManager/Annotations implementation and persisting to an embedded HSQLDB. The relationship between Person and Address is unidirectional though; a Person points to an Address, so let's look at making the mapping bidirectional. In the Address class, we are going to add a set of Persons who reside at the address, the residents:
public class Address {
...
private Set<Person> residents=new HashSet<Person>();
Now, one Address can point to many Persons, so we add a @OneToMany annotation to the access methods for the residents:
@OneToMany
public Set<Person> getResidents() {
return residents;
}
public void setResidents(Set<Person> residents) {
this.residents = residents;
}
And when we set an address on a person, to maintain the relationship, we'd get the residents and add the person to the set. Except that if you tried to access the residents of an address with the example code from last month like so...
Person p=new Person();
p.setName("John Doe");
p.setAddress(address);
savePerson(p);
address.getResidents().add(p);
…
...then you'd get a lazy initialisation error before you even thought about persisting the change. Collections are not fetched when you retrieve an Address object; they are only retrieved when you actually access the field. This is lazy initialisation. But, lazy initialisation can only happen if the object hasn't been detached from the EntityManager. With last month's code, we were passing detached objects around because we created and closed the EntityManager in each data access method. Now, you can work around this by changing the annotation on residents to...
@OneToMany(fetch=FetchType.EAGER)
public Set<Person> getResidents() {
…
...but this forces the persistence layer to always get all the associated data whenever the object is retrieved, this isn't recommended as a general principle; over use can cause large trees of objects to be pulled into memory. If you want to force the loading you can use the EJBQL fetch keyword to do this. We can change the query in findByPostcode and add "left join fetch address.residents" to force the loading of the residents property like so:
Query q=em.createQuery("select address from Address as address
left join fetch address.residents where postcode=:param ");
Collections are generally lazily fetched, most other fields are eagerly fetched; which is why when we retrieve a Person, its address property contains an address object.
There is another issue though even when we've made those changes; we get a transient object exception from Person. This is because our savePerson method doesn't manage the Person given to it, even though it persists it through the EntityManager merge(). The merge() creates a new managed object when it is given a newly created instance and copies data to that new managed object, whereas persist() makes the instance given to it managed.
This is all down to the lifecycle of objects; when you create an object with new, it is in new/transient state. When you persist it, it enters managed state for the life time of the EntityManager you persisted it in. When that EntityManager is closed, the instance becomes detached. You can reattach the instance in another EntityManager with merge, or get a fresh copy using EntityManager's find method with the id of the detached object. There is one more state, removed, which is when the object has been removed from the EntityManager pending deletion. Now, you may wonder, why these different states? Well, it gets rid of the idea of having to have DTOs (Data Transfer Objects), classes dedicated to just being holders for moving returned data to higher levels of the application. Rather than having a DTO and a persisted class, there's just the persisted class which can be detached.
Back in the example code, we can change savePerson() to use persist(), and add a not dissimilar method updatePerson() which uses merge and updateAddress() to do a merge for Address. Now we can complete that address assignment code from earlier;
Person p=new Person();
p.setName("John Doe");
p.setAddress(address);
savePerson(p);
address.getResidents().add(p);
updateAddress(address);
Moving a Person from one Address to another could be done by using updateAddress() after removing or adding the Person from the residents of each address, but moving should be an atomic operation, so let's implement a moveTo method; we start with the 'getting an EntityManager and transaction' preamble:
private void moveTo(Person p,Address a)
{
EntityManager em=emf.createEntityManager();
EntityTransaction tx=em.getTransaction();
tx.begin();
What we will do is use the EntityManager's find method to get managed versions of the entities:
Person managedperson=em.find(Person.class,p.getId());
Address managedaddress=em.find(Address.class,a.getId());
And when we retrieve a managed instance, within the transaction, anything we retrieve from it is managed too, so we can get a managed copy of the current address with:
Address managedoldaddress=managedperson.getAddress();
Now, as we have the managed version we can manipulate them like so:
managedoldaddress.getResidents().remove(managedperson);
managedaddress.getResidents().add(managedperson);
managedperson.setAddress(a);
We now have to commit these changes. As we've manipulated only the managed versions, from a current EntityManager and within a transaction all we need to do is commit the transaction to persist the changes:
tx.commit();
em.close();
}
Let's look at what tables have been created within the database at this point. There are, as you may expect, Person and Address tables. What you may not expect is that there is also an Address_Person table which has been generated to map the residents set with a resident_id and an address_id field. All of the table and field names are derived from the naming of the classes. You can override this with the @Table annotation when declaring the entity, for example;
@Entity
@Table(name="Location")
public class Address
{
would persist the Address class into a table named Location. If you look at the generated field in the database, you'll see that the names of the columns are derived from the property names in the class and in a similar way, you can override the names of the persisted fields using the @Column annotation. If in the Address class, you inserted
…
@Column(name="postalcode")
public String getPostcode() { return postcode; }
Then the postcode property would be persisted in a column named postalcode. @Column also lets us set other database attributes for the persisted field, including uniqueness, length and precision of the actual database column. One time this is useful, even when relying on the default name generation, is when the generated name for a column clashes with an SQL keyword; say we have a field named "select" you'd get errors as the database rejected malformed SQL, so overrriding the column name to say "my_select" would work around that problem.
Let's return to the Address_Person table that was generated. We can eliminate that table by adding a mappedBy parameter to the @OneToMany annotation in Address like so:
@OneToMany(mappedBy="address")
public Set<Person> getResidents() {
which tells the persistence layer to use the address field of Person to map the residents set, and there's no Address_Person table created; the relationship between Address and Person has been tightened up. With this mapping, we can just persist Person with the address property set, and when we retrieve an Address, its residents set is populated using the mapping to select the relevant Person records. Of course, you may not want such an intimate association between entities.
Now we have our QuickExample with a more functional mapping, let us move onto a different subject. The advantage of EJB3/JPA should be that we can easily move to a different persistence library. To try that out, let's move our example code to use the Glassfish reference implementation of EJB3/JPA.
In theory, we should just need to change the persistence.xml file to use Glassfish like this so:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence">
<
persistence-unit name="example">
<provider>
oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider
</provider>
<class>quick.Person</class>
<class>quick.Address</class>
<properties>
<property name="jdbc.driver" value="org.hsqldb.jdbcDriver"/>
<property name="jdbc.connection.string"
value="jdbc:hsqldb:data/example"/>
<property name="toplink.platform.class.name"
value="oracle.toplink.essentials.platform.database.HSQLPlatform"/
>
<property name="jdbc.user" value="sa"/>
<property name="jdbc.password" value=""/>
<property name="ddl-generation" value="dropandcreate"/>
<property name="toplink.logging.level" value="FINE"/>
</properties>
</persistence-unit>
</persistence>
The provider is changed to the toplink version and the properties have been changed to toplink variants for setting driver, connection, username and password. Replacing the Hibernate "dialect" property is a "toplink.plafrom.class.name" property, the "ddl-generation" property replaces Hibernates "hbm2ddl.auto" property, and the "toplink.logging.level" property set to "fine" replaces the Hibernate "showsql" property, with the SQL statements being logged as part of normal logging.
For build libraries, we toss out all the Hibernate libraries and replace them with libraries from the Glassfish distribution; antlr.jar, asm.jar, asm-attrs.jar, javaee.jar and toplink-essentials.jar can be found in the lib directory of Glassfish; download and install it from the Glassfish site. As we are only using libraries from Glassfish, there's no need to run the Glassfish application server. There's one more tweak to do when you run the code though, unique to Toplink, which is you need to inject an agent; you need toplink-essentials-agent.jar and when you run you need to add "-javaagent:{path to the jar}/toplink-essentials-agent.jar" as an argument to the Java VM in your java runtime command line.
And you should be ready to run. As always though when we are in the world of solidifying standards there are some behavioural differences between TopLink and Hibernate EntityManager/Annotations. In the Glassfish example code, you'll find we've made the following changes.
We added an @JoinColumn("address_id") to Person.address's annotations to override an apparent issue in Toplink not handling defaulting correctly. We have to explicitly name the Id properties of Address and Person with @Column(name="ADDRESS_ID") and @Column(name="PERSON_ID") because otherwise Toplink calls both "ID" and makes a bizarre single "ID" column table for the Address_Person table, notably without emitting an error or warning.
We also had to modify the "findBy" methods so they are more specific in their references in the where clause; e.g. "where person.name=:param", instead of "where name=:param" because Toplink is more fussy over naming.
Finally, we have to replace the "System.out.println(ax.getResidents())" in the exercise method with "for(Person pi:ax.getResidents()) System.out.println(pi);". This is because Toplink's lazy-loading implementation has a toString method which doesn't print out the set contents, so we have to iterate through the set. With those changes, we are able to run the example code.
To round this out, let's switch the underlying database away from an embedded HSQL to MySQL server. We'll assume you've got a MySQL server set up already and have downloaded the MySQL JDBC driver and added it to your libraries. All we have to change in that case is properties in persistence.xml:
…
<property name="jdbc.driver" value="org.gjt.mm.mysql.Driver"/>
<property name="jdbc.connection.string"
value="jdbc:mysql://localhost:3306/example"/>
<property name="toplink.platform.class.name"
value="oracle.toplink.essentials.platform.database.
MySQL4Platform"/>
<property name="jdbc.user" value="root"/>
<property name="jdbc.password" value=""/>
…
We tell the persistence layer to use the MySQL JDBC driver and to use the MySQL4Platform classes from Toplink.The settings here are for a local MySQL server with an open root account; you should substitute in usernames and passwords as appropriate. With our simple example code, it's worth also noting that the delay after exercising the database can be removed; there's no need to worry about an embedded database getting around to writing out its data when it's handled by an external SQL server. As you can see, selecting the database is something that can be done at deployment time.
For comparison, the equivalent properties for Hibernate Entitymanager/Annotations to use MySQL would be:
…
<property name="hibernate.connection.driver_class"
value="org.gjt.mm.mysql.Driver"/>
<property name="hibernate.connection.url"
value="jdbc:mysql://localhost:3306/example"/>
<property name="hibernate.dialect"
value="org.hibernate.dialect.MySQLDialectDialect"/>
<property name="hibernate.connection.username" value="root"/>
<property name="hibernate.connection.password" value=""/>
…
This month, we've looked at some more annotations and mapping between entities available in EJB3/JPA, moved the example over to Glassfish/Toplink and switched databases. Next month, we'll look at how to make working on the persisted objects even easier and show how you can use EJB3/JPA to test enterprise classes, without invoking a whole framework for a single test.
You can download the source code for this tutorial at http://www.builderau.com.au/resources/EJB3examples.zip.
DJ Walker-Morgan is a consulting developer, specialising in Java and user-to-user messaging and conferencing.
Rise in <b>Chinese</b>-funded acquisitions could trigger more hurdles http://t.co/0pXBS1HR
7 minutes ago by GeorgeHAllenGA on twitterRise in Chinese-funded acquisitions could trigger more hurdles: By Ellyne Phneah , ZDNet Asia on May 22, 2012 (6... http://t.co/W3SOdw2c
8 minutes ago by MergeAcquire on twitterRT @zdnetasia: CFOs increasingly involved in IT investment decisions. http://t.co/8QrfwOSb
23 minutes ago by 666hellscream on twitterCFOs increasingly involved in IT investment decisions http://t.co/XD1LerFq via @zdnetasia #PrivateCloud #SC2012 #CAPEX
37 minutes ago by HarishAitharaju on twitterRise in Chinese-funded acquisitions could trigger more hurdles. http://t.co/VC3G3m3o
37 minutes ago by zdnetasia on twitterRT @zdnetasia: Rise in Chinese-funded acquisitions could trigger more hurdles. http://t.co/VC3G3m3o
38 minutes ago by wrikent3500 on twitterSo much as we know , MTK6575 extremely integrated frequency1GHz ARM Cortex-A9 processor, the superiority of 3G / HSPA Modem, and help the...
42 minutes ago by y15822137359 on 5 SaaS adoption speed bumps to avoidRise in Chinese-funded acquisitions could trigger more hurdles - ZDNet Asia: Rise in Chinese-funded acquisitions... http://t.co/bZaAQnRL
52 minutes ago by MandAWorldwide on twitterRise in Chinese-funded acquisitions could trigger more hurdles http://t.co/mIsuZjnU http://t.co/erFX4aVv #arcavir
52 minutes ago by V_RaV on twitterhttp://t.co/VNaZtseV Rise in Chinese-funded acquisitions could trigger more hurdles: "Cash r... http://t.co/N0gZZEdR http://t.co/wiqY9ktt
52 minutes ago by RavtachSolution on twitterRise in Chinese-funded acquisitions could trigger more regulatory clearance issues overseas http://t.co/cvLSpTwo #in
53 minutes ago by EllyZDNetAsia on twitterAlibaba seeks $2.3B from shareholders for Yahoo deal. http://t.co/qLRAhRQk
1 hour ago by zdnetasia on twitterCFOs increasingly involved in IT investment decisions. http://t.co/8QrfwOSb
1 hour ago by zdnetasia on twitterOfficial UEFA #EURO2012 app with Orange 2.0 http://t.co/yoAOXTI1 #hotpeopleifollow
1 hour ago by JohnReporter on twitterWhy wouldn't they be?: CFOs increasingly involved in IT investment decisions http://t.co/4gHYrmQy via @zdnetasia
1 hour ago by TheVoxOxCFO on twitterI reckon your view: "CRM is strategy, not software", if a company replicating the approach uses in ERP implementation into CRM, what they...
1 day ago by wykoong on Gartner: Mobile CRM gives better ROI than socialThis video will teach you about the Excel fill handle but also provide you with a workook to download... http://www.youtube.com/watch?v=...
1 day ago by TradeBrother on A quick fill handle trick for Microsoft Excelwaiting...
3 days ago by eapete on What should count in a company's market value?Boy, you've opened a can of worms now.
Wait for the rants & raves.
I was puzzling before this whether to replicate the success formula we executed for a financial institute, and come out with a standard s...
4 days ago by wykoong on Drop the egos, copy ideas, then innovateEchelon 2012 - The Awesomer Tech Event in Asia
Echelon 2012 – SEA’s longest running tech startup event goes Awesomer. Catch 50 of Asia’s most promising startups & over 40 international speakers on June 11-12.
Startup Asia Jakarta showcases new product-ready tech startups. Plus: hackathon, exhibition, and speakers. Use promo code CBSi50 for 50% discount.
ZDNet Asia Intelligent Singapore video series
Featuring inteviews with CXOs who define "intelligence" in their markets and reveal how their companies drive business efficiencies through ICT.