Chapter 11. Advanced Topics in Persistence

E. F. Codd, 1970 said, "Future users of large databanks must be protected from having to know how the data is organized in the machine (the internal representation)".In this chapter, we will look at the advanced topics for Java Persistence that builds on the knowledge we cemented in Chapter 4, Essential Java Persistence and Chapter 5 Object-Relational Mapping with JPA.

Persistence of map collections

The ability to manage persistence capable objects with java.util.Map type collections has been around since JPA 2.0, so this is not strictly a Java EE 7 feature. Map collections are extremely useful for Java applications, where data can be accessed in data types that are organized into key-and-value pairs. This structure of data is redundant from the point of view of a relational database of joined tables. Therefore, the key of a map with a target object is not persisted.

The MapKey relationship

The annotation @javax.persistence.MapKey associates an owning entity with the target entity when data type of the collection is a type of java.util.Map. The key into the map collection is the target primary key of the target entity or the persistent field or property that uniquely identifies the target entity as the value.

There is only one attribute for @MapKey:

Attribute

Type

Description

Default Value

Name

String

This optional attribute specifies the property in the source entity that serves as a key.

N/A – the provider to use the primary key of the associated entity

Let's look an example of map collection associations that relates a table of recording artists to their albums.

The MapKey relationship

This is the code for Artist, which is cut down to save space.

packageje7hb.jpa.advanced.maps;
/* ... omitted ... */

@Entity
public class Artist {
    @Id @Column(name="ARTIST_ID")
privateintartistId;

    @NotEmpty
@Column(nullable = false, name="ARTIST_NAME")
private String artistName;

@OneToMany(mappedBy="artist", cascade = CascadeType.ALL)
@MapKey()
private Map<Integer,Album> albums = new HashMap<>();

public Artist() { }

  /* ... */
}

The relationship between an Artist and their Album instances is one-to-many, usually. The obvious exception to this rule is the compilation album, but this is neither here nor there. Therefore we annotate the bi-directional relationship accordingly with @OneToMany. The @MapKey annotation associates a map collection relationship and by default it configures the key of the collection as the primary key of the target entity, which in this case is the Album entity.

Here is the target entity, Album:

packageje7hb.jpa.advanced.maps;
/* ... omitted ... */

@Entity
public class Album {
    @Id @Column(name="ALBUM_ID")
privateintalbumId;

@Column(nullable = false, name = "ALBUM_TITLE")
    @NotEmpty
private String title;

@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name="ALBUM_ARTIST")
private Artist artist;

@Column(nullable = false, name="RELEASE_DATE")
    @Past @Temporal(TemporalType.DATE)
private Date date;

  /* ... omitted ... */
}

For the Album entity, the @ManyToOne reverses the owning association back to the Artist entity. This is already familiar to you from Chapter 5, Object-Relational Mapping with JPA on entity relationships. We have sprinkled a few of the Bean Validation annotations such as @Past and @NotEmpty in the entities.

Let's look at the integration test for these persistence entities.

@RunWith(Arquillian.class)
public class MapCollectionPersistenceTest {
    @Deployment
public static JavaArchivecreateDeployment() {
		/* ... omitted ... */
    }

    @PersistenceContextEntityManagerem;
    @Resource UserTransactionutx;

    @Test
public void shouldSaveArtistWithAlbum() throws Exception {
        Artist artist1 = new Artist(1002, "Joni Mitchell");
        Album album = new Album();
album.setAlbumId(8002);
album.setArtist(artist1);
album.setTitle("Blue");
album.setDate( new DateTime(1971, 6, 22, 
              0, 0, 0).toDate() );
artist1.getAlbums().put( album.getAlbumId(), album );

utx.begin();
em.persist(artist1);
utx.commit();

        Artist artist2 = em.find(Artist.class, 1002);
assertEquals(artist1.getArtistName(), 
artist2.getArtistName());
assertEqualMaps(artist1.getAlbums(),
artist2.getAlbums() );
    }

static<K,V> void assertEqualMaps(Map<K,V>m1, Map<K,V>m2)
    {	 /* ... */  }
}

The test method shouldSaveArtistWithAlbum() exercises two entities, Artist and Album. As with all entity relationships on the Java side of JPA, the developer is responsible to ensure association is completely defined between the owner and target entities from both directions.

The static method assertEqualsMap() is a custom assertion method that verifies two map collections are the same.

JPA provider creates or expects a database table called ARTIST and ALBUM. There is an extra column called ALBUM_ARTIST that associates the target entity to the source. It is instructive to examine the console debug output for this unit test:

[EL Fine]: sql: -- INSERT INTO ARTIST (ARTIST_ID, ARTIST_NAME) VALUES (?, ?)
  bind => [1002, Joni Mitchell]
[EL Fine]: sql: -- INSERT INTO ALBUM (ALBUM_ID, RELEASE_DATE, ALBUM_TITLE,
ALBUM_ARTIST) VALUES (?, ?, ?, ?)
  bind => [8002, 1971-06-22, Blue, 1002]

The MapKey join column relationship

So far we saw an association with a map collection using a primary key of the target entity. It is possible to go further with a different key for associated entities such that the JPA provider creates or relies upon a database join table. It is also possible to extend a basic join table with additional properties, such that it has the appearance of a fully independent entity bean.

The annotation @MapKeyColumn specifies the mapping for the key column of a map whose key is a basic JPA type. The annotation @MapKeyClass specifies the type of a map key for association, which can be a basic type, an embedded class or another entity.

Applying a @OneToMany annotation with @MapKeyColumn on a field or property informs the JPA provider to create a join table between source and target entities. The developer can configure the name, null-ability, and cascade operations of the join table with @JoinColumn and @JoinColumns annotations.

The annotation @MapKeyJoinColumn specifies the column mapping of the entity that is used as the key. The key join column for the map can be another reference database table, part of a join table, or field referenced in the map collection table.

Developers can create quite sophisticated associations to two or more database tables with the @MapKeyJoinColumn annotation. We will look at an example of code that associates the recording artist to live events and a type of event. An artist is usually involved in a tour, and for the active lifetime of the artist, they will have several tours, and each tour is a type. A tour can be a global( )one, spanning multiple cities on many continents, or it can be exclusive to a set town, television production, a collaborative group effort, or sometimes charitable to a good cause.

How can we represent this business requirement in JPA using map collections?

Let's start with a new version of the Artist entity:

@Entity
public class Artist {
    @Id @Column(name="ARTIST_ID") private long id;
@Column(nullable=false, name="ARTIST_NAME")
private String name;

@OneToMany(mappedBy="artist",cascade = CascadeType.ALL)
@MapKeyJoinColumn(name="EVENT_TYPE_ID")
private Map<EventType, LiveEvent> events 
          = new HashMap<>();
public Artist() {}   /*...*/
}

The biggest change is the parameterized type for the map collection, which is Map<EventType, LiveEvent>, where we apply the @MapKeyJoinColumn. The key column is a part of an entity, EventType, and has the database column name EVENT_TYPE_ID.

Here is the persistence entity EventType; it only has two properties, the primary key and the name:

@Entity @Table(name="EVENT_TYPE")
public class EventType {
    @Id @Column(name="EVENT_TYPE_ID") private long id;
    @Basic @Column(name="EVENT_TYPE_NAME")
private String type;
publicEventType() {}      /*...*/
}

Because we overrode the default column name in JPA for the primary key to EVENT_TYPE_ID, we must also configure the @MapKeyJoinColumn with the same target database column name.

Finally, we have the entity for storing data about events called LiveEvent:

@Entity @Table(name="LIVE_EVENT")
public class LiveEvent {
    @Id @Column(name="LIVE_EVENT_ID") private long id;
@Column(nullable=false, name="ARTIST_NAME")
private String name;
@OneToOne(cascade = CascadeType.ALL)
privateEventTypeeventType;

@ManyToOne(cascade = CascadeType.ALL)
private Artist artist;

publicLiveEvent() {} 		/*...*/
}

We declare a @OneToOne unidirectional association for the join table between the entities: LiveEvent and EventType. If we preferred to share event types among live events, we could change to the association to @OneToMany. We declare a bidirectional reverse association @ManyToOne from LiveEvent to Artist.

Running the actual unit test from the book's source code creates three database tables: ARTIST, EVENT_TYPE and LIVE_EVENT. The JPA provider adds an additional column, EVENTTYPE_EVENT_TYPE_ID, to the LIVE_EVENT table.

Again, it is helpful to examine the JPA console output:

[EL Fine]: sql: -- INSERT INTO ARTIST (ARTIST_ID, ARTIST_NAME) VALUES (?, ?)
  bind => [1002, Lady Gaga]
[EL Fine]: sql: -- INSERT INTO EVENT_TYPE (EVENT_TYPE_ID, 
EVENT_TYPE_NAME) VALUES (?, ?)
  bind => [808, WORLD TOUR]
[EL Fine]: sql: -- INSERT INTO LIVE_EVENT (LIVE_EVENT_ID, ARTIST_NAME, 
ARTIST_ARTIST_ID, EVENTTYPE_EVENT_TYPE_ID) VALUES (?, ?, ?, ?)
  bind => [97502, The Monster Ball Tour, 1002, 808]
[EL Fine]: -- UPDATE LIVE_EVENT SET EVENT_TYPE_ID= ? WHERE 
   (LIVE_EVENT_ID= ?)
  bind => [808, 97502]

Let's now move on to stored procedures.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.76.204