Thursday, March 1, 2012

JPA Tutorial

Java persistent API is a specification and set of interfaces that helps with persistence of java objects into the database. It has been created after success of object relational mapping frameworks - the most popular is Hibernate.

JPA defines standard basic set of features that every object relational mapping framework should have. Almost all current O/R frameworks implement JPA specification. Of course, most of them have additional features to set them apart.

Saving/loading data with JPA is easier, faster and less error prone than writing SQL and mapping query results to objects. However, JPA does not free you from relational database understanding. And while you do not have to write SQL anymore, you have to learn JPQL which is very similar.

First three chapters of this post contain configuration and set up. Following three chapters explain what are entities and show how to create, save and load them. The rest of the post explains how to create and configure relationships between entities.

Table of Contents

Demo Project

All examples and test cases used in this tutorial are available in a project on Github. It is a plain Maven application, no configuration is necessary.

The project uses an in-memory Derby database which is created before each test case and destroyed after each test case. It is lightweight and leaves no traces behind it.

We used Liquibase to maintain the database structure, mostly because it is practical for project that has to drop and re-create the database often. It keeps both database description and initial data in a 'changelog' xml file.

Our changelog files are named db.*.changelog.xml and are always stored in the same directory as the test case that uses them.

Other set-up details are not necessary to understand. If you are interested in them anyway, we described them in an independent post.

JPA Implementation

There are plenty of JPA implementations and they should all work the same way. We used OpenJPA in our demo project, mostly because it is free, open source and easy to set up.

This chapter contains OpenJPA specific configuration. Everything else in article works the same way regardless of chosen JPA provider.

First, define OpenJPA version property in pom.xml:
<properties>
 <openjpa.version>2.1.1</openjpa.version>
</properties>

Dependency
Add OpenJPA dependency into pom.xml:
<dependency>
 <groupId>org.apache.openjpa</groupId>
 <artifactId>openjpa-all</artifactId>
 <version>${openjpa.version}</version>
</dependency>

Plugin
OpenJPA requires openjpa-maven-plugin maven plugin. It runs at compile time and modifies compiled java classes to work with OpenJPA. This process is called enhancement.

Openjpa-maven-plugin must know about all compiled entity classes. What entities are will be explained later in this article. For now, you can use a simple rule: if it has JPA annotation on it, openjpa-maven-plugin must know about it.

Add openjpa-maven-plugin into pom.xml and specify all entity classes inside the includes sub-tag. The tag supports *:
<plugin>
 <groupId>org.codehaus.mojo</groupId>
 <artifactId>openjpa-maven-plugin</artifactId>
 <version>1.2</version>
 <configuration>
  <!-- beginning of directories with compiled entity classes -->
  <includes>org/meri/jpa/*/entities/*/*.class</includes>
  <includes>org/meri/jpa/*/entities/*.class</includes>
  <!-- end of directories with compiled entity classes -->
  <addDefaultConstructor>true</addDefaultConstructor>
  <enforcePropertyRestrictions>true</enforcePropertyRestrictions>
 </configuration>
 <executions>
  <execution>
   <id>enhancer</id>
   <phase>process-classes</phase>
   <goals>
    <goal>test-enhance</goal>
    <goal>enhance</goal>
   </goals>
  </execution>
 </executions>
 <dependencies>
  <dependency>
   <groupId>org.apache.openjpa</groupId>
   <artifactId>openjpa</artifactId>
   <version>${openjpa.version}</version>
  </dependency>
 </dependencies>
</plugin>

Eclipse
If you use Eclipse with m2e plugin, you have to add also following huge thing into your pom.xml. It removes the 'Plugin execution not covered by lifecycle configuration' error:
<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
<plugin>
 <groupId>org.eclipse.m2e</groupId>
 <artifactId>lifecycle-mapping</artifactId>
 <version>1.0.0</version>
 <configuration>
  <lifecycleMappingMetadata>
   <pluginExecutions>
    <pluginExecution>
     <pluginExecutionFilter>
      <groupId>
       org.codehaus.mojo
      </groupId>
      <artifactId>
       openjpa-maven-plugin
      </artifactId>
      <versionRange>
       [1.0,)
      </versionRange>
      <goals>
       <goal>test-enhance</goal>
       <goal>enhance</goal>
      </goals>
     </pluginExecutionFilter>
     <action>
      <execute><runOnIncremental>true</runOnIncremental></execute>
     </action>
    </pluginExecution>
   </pluginExecutions>
  </lifecycleMappingMetadata>
 </configuration>
</plugin>

Configuration

JPA configuration is kept in META-INF/persistence.xml file. If you are using Maven, the full path is src/main/resources/META-INF/persistence.xml .

Overview
The persistence.xml file contains description of one or more persistence units. For readability sake, we omitted schema location and version from the next example. Full version is available on Github:
<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns:xsi="..." xsi:schemaLocation="..."version="2.0" xmlns="...">
    
    <persistence-unit name="Name1" transaction-type="RESOURCE_LOCAL">
        ... 
    </persistence-unit>

    <persistence-unit name="NameN" transaction-type="RESOURCE_LOCAL">
        ...
    </persistence-unit>
</persistence>

Persistence unit roughly corresponds to a database and most applications have only one persistence unit. Each persistence unit configuration specifies:
  • JPA persistence provider to be used,
  • database connection,
  • list of all entities,
  • other vendor specific configuration properties.

Each JPA implementation have different persistence provider. For example, OpenJPA uses org.apache.openjpa.persistence.PersistenceProviderImpl and Hibernate uses org.hibernate.ejb.HibernatePersistence.

Each entity must be listed separately, the * is not supported.

Example
Our example persistence unit is named 'Simplest'. It uses OpenJPA provider and connects to the database via data source stored in JNDI. The persistence unit contains two entities Person and AnotherEntity. It also logs all queries applied to the database:
<persistence-unit name="Simplest" transaction-type="RESOURCE_LOCAL">
    <!-- OpenJPA persistence provider -->
    <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
    <!-- JNDI name of the datasource -->
    <jta-data-source>jdbc/jpaDemoDB</jta-data-source>

    <!-- JPA entities must be registered here -->
    <class>org.meri.jpa.simplest.entities.Person</class>
    <class>org.meri.jpa.simplest.entities.AnotherEntity</class>
        
    <!-- other properties -->
    <properties>
        <!-- Log all queries performed against the database. -->
        <!-- Do not use in production, this will generate a lot of output. -->
        <property name="openjpa.Log" value="SQL=TRACE"/>
    </properties>
</persistence-unit>

Entity

Entity is an ordinary java class with properties, getters and setters. It has to be marked as an entity with the @Entity annotation and must belong to some persistence unit. It must have a no arguments constructor.

A simplest possible entity has no relationships to other entities and corresponds to one database table. Each its property corresponds to a database column. Each entity must have unique id marked with the @Id annotation. Of course, unique id may be composed of multiple columns.

It is not possible to change the id once it was assigned. JPA behavior is undefined if this occurs.

JPA assumes that the table name is the same as entity class name. Use the @Table annotation to customize the table name. JPA also assumes that the column name is the same is the property name. Use the @Column annotation to customize it.

Following entity 'Person' corresponds to database table named 'person'. The @Table annotation is not necessary in this case, the names are the same. The entity has unique id 'id' which corresponds to the 'user_id' column. All remaining properties correspond to columns with the default name.
@Entity
@Table(name = "Person")
public class Person {

  @Id
  @Column(name = "user_id")
  private long id;

  private String userName;
  private String firstName;
  private String lastName;
  private String homePage;
  private String about;

  public long getId() {
    return id;
  }

  public void setId(long id) {
    this.id = id;
  }

  // all remaining getters and setters
}
Note: @Table annotation allows also customization of db schema and catalog.

Person registration in 'Simples' persistence unit in persistence.xml file:
<persistence-unit name="Simplest" transaction-type="RESOURCE_LOCAL">
  <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
  <jta-data-source>jdbc/jpaDemoDB</jta-data-source>
  <!-- JPA entities must be registered here -->
  <class>org.meri.jpa.simplest.entity.Person</class>
</persistence-unit>

Corresponding database structure:
CREATE TABLE person (
    user_id INT NOT NULL, 
    username VARCHAR(1500) NOT NULL, 
    firstname VARCHAR(1500), 
    lastname VARCHAR(1500), 
    homepage VARCHAR(1500), 
    about VARCHAR(1500), 
    CONSTRAINT 
        PK_PERSON PRIMARY KEY (user_id), 
        UNIQUE (username)
);

Entity Manager Factory

Each application should have one entity manager factory per persistence unit. Entity manager factory is thread safe. It should open while application starts and close on application shutdown. As the name suggests, entity manager factory is able to create an entity manager.

Create entity manager factory corresponding to the 'Simplest' persistence unit:
private static final String PERSISTENCE_UNIT = "Simplest";

private EntityManagerFactory createFactory() {
  return Persistence.createEntityManagerFactory(PERSISTENCE_UNIT);
}

Entity Manager

Entity manager is responsible for loading, saving and otherwise managing entities. It keeps track of all loaded entities in so-called persistence context. Neither entity manager nor entities are thread safe.

This chapter shows most important entity manager features. First, we will show how to use it to load entities. Then, we will explain how to remove entities form the entity manager and how to add new entities into it. Finally, we will show how to modify the database and how to force reload of an entity.

All examples used in this chapter are available in LoadEntityTest class on Github.

Obtaining
Obtain the entity manager from the entity manager factory:
EntityManager em = getFactory().createEntityManager();

Loading
Entity manager generates SQL statements to load entities, creates entity objects and composes them into object trees. All loaded entities are cached in something called persistence context. In other words, loaded entities are attached to entity manager.

One benefit of caching in the persistence context is that entities with the same ids correspond to equal objects.

Next example uses the find method to load entities by their ids. Loaded entities are equal:
private static final BigDecimal SIMON_SLASH_ID = BigDecimal.valueOf(1);

@Test
public void entityEquality_same_EntityManager() {
  EntityManager em = factory.createEntityManager();
  Person person1 = em.find(Person.class, SIMON_SLASH_ID);
  Person person2 = em.find(Person.class, SIMON_SLASH_ID);
  em.close();
    
  //entities are equal
  assertEquals(person1, person2);
  //changes in one entity are visible in second entity
  person1.setFirstName("nobody");
  assertEquals("nobody", person2.getFirstName());
}

Different entity managers are completely independent of each other. If you use two different entity managers to load entities, they will result in two different independent objects.

Load two entities with two entity managers and compare them. They are not the same:
@Test
public void entityEquality_different_EntityManager() {
  EntityManager em1 = factory.createEntityManager();
  Person person1 = em1.find(Person.class, SIMON_SLASH_ID);
  em1.close();

  EntityManager em2 = factory.createEntityManager();
  Person person2 = em2.find(Person.class, SIMON_SLASH_ID);
  em2.close();

  //entities are different
  assertNotSame(person1, person2);
  //entities are independent
  person1.setFirstName("nobody");
  assertNotSame("nobody", person2.getFirstName());
}

Beware: Be careful about closing entity managers. It automatically detaches all entities. Detached entity looses access to all JPA features. For example, lazy loading on detached entities does not work.

Detaching Entities
Closing entity manager is not the only way how to detach all entities. Use the clear method on the entity manager to detach all entities without closing it.
Click to expand the test case:

/**
 * Test the clear method of entity manager. Load an entity, 
 * clear the entity manager and load the same entity again. 
 * Loaded entities are different.
 */
@Test
public void entityEquality_cleared_EntityManager() {
  EntityManager em = factory.createEntityManager();

  Person person1 = em.find(Person.class, SIMON_SLASH_ID);
  em.clear();
  Person person2 = em.find(Person.class, SIMON_SLASH_ID);
  em.close();

  assertNotSame(person1, person2);
}

If you wish to detach only one entity, use the method detach:
Click to expand the test case:

/**
 * Test the detach method of entity manager. Load an entity, 
 * detach it and load the same entity again. 
 * Loaded entities are different.
 */  
@Test
public void entityEquality_detach() {
  EntityManager em = factory.createEntityManager();

  Person person1 = em.find(Person.class, SIMON_SLASH_ID);
  em.detach(person1);
  Person person2 = em.find(Person.class, SIMON_SLASH_ID);
  em.close();

  assertNotSame(person1, person2);
}

Merge
It is not possible to re-attach detached entity. However, it is possible to merge its data back to the entity manager.

Merge will not attach the entity, it only copies all its data into entity manager. Changed data are not available in different entity manager:
Click to expand the test case:

/**
 * The merge method should copy all entity data into 
 * entity manager. Entity itself in not attached nor
 * persisted.
 */
@Test
public void entityEquality_mergeInto_EntityManager() {
  //load and detach entity
  EntityManager em1 = factory.createEntityManager();
  Person person1 = em1.find(Person.class, SIMON_SLASH_ID);
  em1.close();

  //change its property
  person1.setFirstName("New Name");

  //merge it into some entity manager, data are copied
  EntityManager em2 = factory.createEntityManager();
  em2.merge(person1);

  //this change will be ignored
  person1.setFirstName("Ignored Change");

  //load another instance of the same person
  Person person2 = em2.find(Person.class, SIMON_SLASH_ID);
  em2.close();

  //entity itself was not attached 
  assertNotSame(person1, person2);
  //before merge changes are available; after merge changes are ignored
  assertEquals("New Name", person2.getFirstName());

  // changed data are NOT available in different entity manager
  EntityManager em3 = factory.createEntityManager();
  Person person3 = em3.find(Person.class, SIMON_SLASH_ID);
  em3.close();

  assertNotSame("New Name", person3.getFirstName());
}

Updates, Inserts and Deletes
Modifying data stored in the database with JPA and SQL is similar. You have to open the transaction, perform updates, inserts and deletes and commit the transaction.

This chapter starts with an overview and short sections on commits, rollbacks and error handling. Each of following sections explains one persistence operation, e.g. update, insert and delete.

Overview
Entity manager monitors all changes done on attached entities. If you open and commit the transaction, entity manager will generate SQL queries corresponding to all changes and apply them to the database.

Of course, only changes done on attached entities are persisted. Detached entities are ignored.

Commit and Rollback
Just as with SQL, commit saves data into the database and rollback throws them away. However, rollback has one important side effect - it detaches all loaded entities.
Click to expand the test case:

/**
 * Test the side effect of the rollback method. All 
 * loaded entities are detached.
 */
@Test
public void entityEquality_rollbacked_transaction() {
  EntityManager em = factory.createEntityManager();
  //load entity and rollback the transaction
  em.getTransaction().begin();
  Person person1 = em.find(Person.class, SIMON_SLASH_ID);
  em.getTransaction().rollback();

  //load the same entity again
  em.getTransaction().begin();
  Person person2 = em.find(Person.class, SIMON_SLASH_ID);
  em.getTransaction().commit();

  em.close();
  //second entity is different from the first one
  assertNotSame(person1, person2);
}

Exceptions
If the SQL statement causes a database error, for example integrity constraint violation or unique constraint violation, JPA throws runtime exception either during the operation or during the commit.

You have to handle possible exception in both places.

Update
To update the database, change properties of an attached entity and commit a transaction.
EntityManager em = factory.createEntityManager();
//open transaction
em.getTransaction().begin();
Person person1 = em.find(Person.class, SIMON_SLASH_ID);
//change the entity
person.setFirstName("nobody");
//commit data
em.getTransaction().commit();

Next example updates a person and saves the change to the database. A different entity manager then loads the same person and checks the change:
Click to expand the test case:

@Test 
public void updateEntity() {
  EntityManager em1 = factory.createEntityManager();
  //open transaction
  em1.getTransaction().begin();
  Person person1 = em1.find(Person.class, SIMON_SLASH_ID);
  //change the entity
  person1.setFirstName("nobody");
  //commit data
  em1.getTransaction().commit();
  em1.close();

  //load another entity
  EntityManager em2 = factory.createEntityManager();
  Person person2 = em2.find(Person.class, SIMON_SLASH_ID);
  em2.close();

  // entities are different
  assertNotSame(person1, person2);
  // second entity contains all commited changes
  assertEquals("nobody", person2.getFirstName());
}

Note: entity change does not have to happen in between transaction begin and commit. Following works too:
Click to expand the test case:

@Test 
public void updateEntity_note() {
  EntityManager em1 = factory.createEntityManager();
  Person person1 = em1.find(Person.class, SIMON_SLASH_ID);
  //change the entity
  person1.setFirstName("hello");
  //open transaction and commit data
  em1.getTransaction().begin();
  em1.getTransaction().commit();
  em1.close();

  // ... load and check ...
}

Changes on detached entities are ignored. The next test detaches an entity and commits a transaction. Changes are not saved into the database:
Click to expand the test case:

@Test 
public void detachedEntity() {
  EntityManager em1 = factory.createEntityManager();
  //open transaction
  em1.getTransaction().begin();
  Person person1 = em1.find(Person.class, SIMON_SLASH_ID);
  //change and detach the entity
  person1.setFirstName("detacheEntity");
  em1.detach(person1);
  //commit data
  em1.getTransaction().commit();
  em1.close();

  //load another entity
  EntityManager em2 = factory.createEntityManager();
  Person person2 = em2.find(Person.class, SIMON_SLASH_ID);
  em2.close();

  // entities are different
  assertNotSame(person1, person2);
  // the change was not saved
  assertNotSame("detacheEntity", person2.getFirstName());
}

Insert
To insert a new row into the database, a corresponding entity must be introduced to the persistence context. There are two ways how to do it:
  • persist attaches new entity to the entity manager. This method works only if an entity with the same id does not exists,
  • merge adds entity data into the entity manager. The entity is not attached to the entity manager.

Use the persist method:
Person newPerson = new Person(3, "BB", "Bob", "Brandert");

EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
//persist the entity 
em.persist(newPerson);
em.getTransaction().commit();

Next test creates a new person, persists it and commits changes. Saved person is loaded with different entity manager and checked:
Click to expand the test case:

@Test 
public void insertEntityPersist() {
  Person newPerson = new Person(3, "BB", "Bob", "Brandert");

  EntityManager em1 = factory.createEntityManager();
  em1.getTransaction().begin();
  //persist the entity 
  em1.persist(newPerson);
  em1.getTransaction().commit();
  //the entity was attached 
  assertTrue(em1.contains(newPerson));
  em1.close();
    
  //new entity was saved and is available to different entity manager
  EntityManager em2 = factory.createEntityManager();
  Person person2 = em2.find(Person.class, 3);
  em2.close();

  // check loaded entity
  assertEquals("BB", person2.getUserName());
}

If an entity with the same id already exists, either persist or a commit method throws an exception:
Click to expand the test case:

@Test 
public void insertEntityPersist_Note() {
  Person newPerson = new Person(4, "CC", "Cyntia", "Crowd");

  EntityManager em1 = factory.createEntityManager();
  em1.getTransaction().begin();
  //persist the entity 
  em1.persist(newPerson);
  em1.getTransaction().commit();
  em1.close();

  Person otherPerson = new Person(4, "CC", "Cyntia", "Crowd");
  EntityManager em2 = factory.createEntityManager();
  em2.getTransaction().begin();
  //persist the entity 
  try {
    em2.persist(otherPerson);
    em2.getTransaction().commit();
  } catch (PersistenceException ex) {
    // from the API: The EntityExistsException may be thrown 
    // when the persist operation is invoked, or the 
    // EntityExistsException or another PersistenceException 
    // may be thrown at flush or commit time.
    em2.close();
    return ;
  }
    
  fail("Either persist or commit should throw an exception.");
}

Use the merge method:
Person newPerson = new Person(2, "MM", "Martin", "Martinez");

EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
//merge entity properties into persistence context 
em.merge(newPerson);
em.getTransaction().commit();

Create a new person, merge it into the persistence context and commit changes. Merged person is loaded with different entity manager and checked:
Click to expand the test case:

@Test 
public void insertEntityMerge() {
  Person newPerson = new Person(2, "MM", "Martin", "Martinez");

  EntityManager em1 = factory.createEntityManager();
  em1.getTransaction().begin();
  //merge entity properties into persistence context 
  em1.merge(newPerson);
  em1.getTransaction().commit();
  em1.close();

  //new entity was saved and is available to different entity manager
  EntityManager em2 = factory.createEntityManager();
  Person person2 = em2.find(Person.class, 2);
  em2.close();

  // check loaded entity
  assertEquals("MM", person2.getUserName());
}

Delete
Use the remove method to delete an entity:
EntityManager em = factory.createEntityManager();
    
//delete an entity
em.getTransaction().begin();
Person person1 = em1.find(Person.class, SIMON_SLASH_ID);
em.remove(person1);
em.getTransaction().commit();

Delete the entity and check whether it was deleted:
Click to expand the test case:

@Test
public void deleteEntity() {
  EntityManager em1 = factory.createEntityManager();
    
  //delete an entity
  em1.getTransaction().begin();
  Person person1 = em1.find(Person.class, SIMON_SLASH_ID);
  em1.remove(person1);
  em1.getTransaction().commit();
  em1.close();

  //try to load deleted entity
  EntityManager em2 = factory.createEntityManager();
  Person person2 = em2.find(Person.class, SIMON_SLASH_ID);
  em2.close();

  //entity does not exist anymore
  assertNull(person2);
}

Refresh
Typically, a thread loads an entity and uses it for a while. If another thread updated the database in between, loaded data are no longer accurate.

Use the method refresh to update the entity with all changes from the database. Refreshed entity contains only freshly loaded data, e.g. any changes made to the entity are lost. The refresh method can not be used on detached entities.
EntityManager em = factory.createEntityManager();
Person person1 = em.find(Person.class, SIMON_SLASH_ID);
//  ...
em.refresh(person1);

Refresh test loads an entity with two different entity managers. One updates the entity and another refreshes it. The test checks whether refreshed entity contains changes:
Click to expand the test case:

@Test
public void entityEquality_refresh_EntityManager() {
  // load an entity
  EntityManager em1 = factory.createEntityManager();
  Person person1 = em1.find(Person.class, SIMON_SLASH_ID);

  // load the same entity by different entity manager
  EntityManager em2 = factory.createEntityManager();
  Person person2 = em2.find(Person.class, SIMON_SLASH_ID);

  //change the first entity - second entity remains the same
  em1.getTransaction().begin();
  person1.setFirstName("refreshDemo");
  assertNotSame("refreshDemo", person2.getFirstName());
    
  //commit the transaction - second entity still remains the same
  em1.getTransaction().commit();
  assertNotSame("refreshDemo", person2.getFirstName());

  //refresh second entity - it changes
  em2.refresh(person2);
  assertEquals("refreshDemo", person2.getFirstName());

  em1.close();
  em2.close();
}

If the other thread deleted the entity, the refresh method throws an EntityNotFoundException exception:
Click to expand the test case:

@Test
public void refreshDeletedEntity() {
  EntityManager em1 = factory.createEntityManager();
    
  //load the entity
  EntityManager em2 = factory.createEntityManager();
  Person person2 = em2.find(Person.class, SIMON_SLASH_ID);

  //delete it
  em1.getTransaction().begin();
  Person person1 = em1.find(Person.class, SIMON_SLASH_ID);
  em1.remove(person1);
  em1.getTransaction().commit();
  em1.close();

  //refresh loaded entity
  try {
    em2.refresh(person2);
  } catch(EntityNotFoundException ex) {
    return ;
  } finally {
    em2.close();
  }
    
  fail("An exception was expected.");
}

The refresh on detached entity throws an exception:
Click to expand the test case:

@Test
public void refreshDetachedEntity() {
  EntityManager em = factory.createEntityManager();
  Person person1 = em.find(Person.class, SIMON_SLASH_ID);

  em.detach(person1);
  try {
    em.refresh(person1);
  } catch (IllegalArgumentException ex) {
    em.close();
    return ;
  }

 fail("An exception was expected.");
}

Relationships

JPA relationship is a reference going from one entity to another entity. In java, relationship is a property that contains entity or collection of entities. In database, relationship corresponds to foreign key between entity tables.

A Java code with relationship between person and twitter account entities. JPA annotations will be explained later:
@Entity
public class TwitterAccount {
  @ManyToOne
  private Person owner;
}

@Entity
public class Person {
  @OneToMany(mappedBy="owner")
  private Collection<TwitterAccount> twitterAccounts;
}

Before we explain which relationship types exist and how to configure them, we have to explain few terms and concepts. Some of them are frequently used in JPA documentation and needed to describe how to configure entity relationships. Others hide little traps for users, so it is better to know about them before you start to code.

Lazy Loading
Most relationships are by default lazy loaded. That means, that data are read from database only if they are needed.
@Test
public void lazyLoading() {
  EntityManager em = factory.createEntityManager();
    
  // load the person, twitter accounts are not loaded yet
  Person simon = em.find(Person.class, SIMON_SLASH_ID);
    
  // load twitter accounts from the database
  Collection<TwitterAccount> accounts = simon.getTwitterAccounts();
  assertEquals(2, accounts.size());

  // closing entity manager, lazy loading will not be possible anymore
  em.close();
}

This has an important consequence: if you close entity manager or detach the entity, the relationship information will not be available.

If your relationship returns null (OpenJPA) or throws an exception (Hibernate), check whether entity is still attached to the entity manager and whether entity manager was not closed by mistake. This type of error is pretty common, especially as all examples, including ours, always dutifully close used entity managers.

Closed entity manager is not able to lazy load data:
Click to expand the test case:

@Test
public void lazyLoadingCloseWarning() {
  EntityManager em = factory.createEntityManager();
  Person simon = em.find(Person.class, SIMON_SLASH_ID);
  em.close();
    
  assertNull(simon.getTwitterAccounts());
}

Detached entity is not able to lazy load data:
Click to expand the test case:

@Test
public void lazyLoadingDetachWarning() {
  EntityManager em = factory.createEntityManager();

  Person simon = em.find(Person.class, SIMON_SLASH_ID);
  em.detach(simon);
  assertNull(simon.getTwitterAccounts());
    
  em.close();
}

Bidirectional vs Unidirectional
Any relationship is either 'bidirectional' or 'unidirectional'.

Lets say the entity A references another entity B. On the java side, the reference B may or may not keep reference back to the entity A. If the entity B references the entity A, the relationship is called 'bidirectional'. If the entity B does not know about the entity A, the relationship is 'unidirectional'.

Warning: If you use bidirectional relationships, you have to ensure their consistency. If the entity A references the entity B, the entity B must reference back to A. Inconsistent bidirectional relationship may cause unpredictable JPA behavior.

Self-Consistent Entities
The safest way to achieve the consistency is to create 'smart' setters that always maintain relationship consistency.

For example, the setOwner method of the TwitterAccount class may automatically remove the twitter account from the old owner and add it to the new owner:
Click to collapse

/**
 * Set new twitter account owner. The method keeps 
 * relationships consistency:
 * * this account is removed from the previous owner
 * * this account is added to next owner
 * 
 * @param owner
 */
public void setOwner(Person owner) {
  //prevent endless loop
  if (sameAsFormer(owner))
    return ;
  //set new owner
  Person oldOwner = this.owner;
  this.owner = owner;
  //remove from the old owner
  if (oldOwner!=null)
    oldOwner.removeTwitterAccount(this);
  //set myself to new owner
  if (owner!=null)
    owner.addTwitterAccount(this);
}

private boolean sameAsFormer(Person newOwner) {
  return owner==null? newOwner == null : owner.equals(newOwner);
}

'Safe' person class on the other side of the relationship would not expose the twitterAccounts collection directly. Instead, it would have specialized addTwitterAccount and removeTwitterAccount methods:
Click to collapse

/**
 * Returns a collection with owned twitter accounts. The 
 * returned collection is a defensive copy.
 *  
 * @return a collection with owned twitter accounts
 */
public Collection<TwitterAccount> getTwitterAccounts() {
  //defensive copy, nobody will be able to change the 
  //list from the outside
  return new ArrayList<TwitterAccountBetter>(twitterAccounts);
}

/**
 * Add new account to the person. The method keeps 
 * relationships consistency:
 * * this person is set as the account owner
 */
public void addTwitterAccount(TwitterAccount account) {
  //prevent endless loop
  if (twitterAccounts.contains(account))
    return ;
  //add new account
  twitterAccounts.add(account);
  //set myself into the twitter account
  account.setOwner(this);
}

/**
 * Removes the account from the person. The method keeps 
 * relationships consistency:
 * * the account will no longer reference this person 
 *   as its owner
 */
public void removeTwitterAccount(TwitterAccount account) {
  //prevent endless loop
  if (!twitterAccounts.contains(account))
    return ;
  //remove the account
  twitterAccounts.remove(account);
  //remove myself from the twitter account
  account.setOwner(null);
}

Note: most our examples and test cases do not use the safer form. It is mostly because we wanted to keep them as simple as possible. The production code should use the safer version whenever possible.

Owning Side vs Inverse Side
The terms 'owner side' and 'inverse side' are frequently used in JPA documentation and API. They are important because it is impossible to describe how to configure entity relationships without them.

Entities correspond to database tables and their relationships should correspond to foreign keys between those tables.

If the tables are joined directly, one of them have column with foreign key pointing to the other entity. The entity stored in the table with the foreign key is called the 'owning side'. The entity referenced by the foreign key is called the 'inverse side'. Unidirectional relationship has only owning side.

If the tables are joined indirectly through some joining table, any side can be designed as the owning side. It does not matter.

Persisting Relationships
JPA assumes that the model is consistent. E.g. entities in bidirectional relationships references each other and deleted entities have been removed from all their relationships.

If you keep the model consistent, JPA will generally work as intuitively expected. Few less intuitive features are described in following sub-chapters.

Relationship Changes
The relationship between two entities is saved whenever the owner is saved. Saving the inverse does nothing to the relationship, it is only the owner who counts.
Click to expand the test case:
Quote from the specification, page 41: "The owning side of a relationship determines the updates to the relationship in the database."

Quote from the specification, page 78: "Bidirectional relationships between managed entities will be persisted based on references held by the owning side of the relationship. It is the developer’s responsibility to keep the in-memory references held on the owning side and those held on the inverse side consistent with each other when they change. In the case of unidirectional one-to-one and one-to-many relationships, it is the developer’s responsibility to insure that the semantics of the relationships are adhered to."
Saving the owner is enough to save the relationship:
Click to expand the test case:

@Test
public void relationshipSaveOnlyOwner_oto() {
  EntityManager em = getFactory().createEntityManager();
  // load two entities
  OneToOneOwner owner = em.find(OneToOneOwner.class, 6);
  OneToOneInverse inverse = em.find(OneToOneInverse.class, 6);
  // create a relationship between entities
  owner.setInverse(inverse);
  inverse.setOwner(owner);
  // detached inverse will be ignored by commit
  em.detach(inverse);
  em.getTransaction().begin();
  // persist only the owner - it is the only loaded entity
  em.getTransaction().commit();
  em.close();

  // relationship was saved
  EntityManager em1 = getFactory().createEntityManager();
  OneToOneInverse inverseCheck = em1.find(OneToOneInverse.class, 6);
  assertNotNull(inverseCheck.getOwner());
  em1.close();
}

Saving only the inverse does not save the relationship:
Click to expand the test case:

@Test
public void relationshipSaveOnlyInverse_oto() {
  EntityManager em = getFactory().createEntityManager();
  // load two entities
  OneToOneOwner owner = em.find(OneToOneOwner.class, 7);
  OneToOneInverse inverse = em.find(OneToOneInverse.class, 7);
  // create a relationship between entities
  owner.setInverse(inverse);
  inverse.setOwner(owner);
  // detached owner will be ignored by commit
  em.detach(owner);
  em.getTransaction().begin();
  // persist only the inverse - it is the only loaded entity
  em.getTransaction().commit();
  em.close();

  // relationship was not saved
  EntityManager em1 = getFactory().createEntityManager();
  OneToOneOwner ownerCheck = em1.find(OneToOneOwner.class, 7);
  assertNull(ownerCheck.getInverse());
  em1.close();
}

Entity Dependencies
As the owners table keeps reference to the inverse table, what happen when you persist the owner before the inverse? Will it blindly follow the order in which you wrote those commands and throw an exception on foreign key violation? Will it reorder the statements so that everything works?

JPA specification does not say how smart an implementation should be. It may do either of these things. That being said, any decent JPA provider is able to automatically resolve entity dependencies. You do not have to persist or delete entities in some special order, JPA will reorder generated SQL statements so that everything works.
Click to Expand

/**
 * Any decent JPA provider can be configured to automatically
 * resolve relationship dependencies. However, it is not 
 * required by the specification.
 * 
 * It will reorder those inserts so that no exception is thrown.
 */
@Test
public void wrongOrder_oto() {
  // create a relationship between entities
  OneToOneOwner owner = new OneToOneOwner(888);
  OneToOneInverse inverse = new OneToOneInverse(888);
  owner.setInverse(inverse);
  inverse.setOwner(owner);

  // persist the owner first - JPA is configured to solve dependencies
  EntityManager em = getFactory().createEntityManager();
  em.getTransaction().begin();
  em.persist(owner);
  em.persist(inverse);

  em.getTransaction().commit();
  
  //check saved data
  EntityManager em1 = getFactory().createEntityManager();
  OneToOneInverse inverseCheck = em1.find(OneToOneInverse.class, 888);
  assertNotNull(inverseCheck.getOwner());
  em1.close();
}

Hibernate does that automatically, but if you use OpenJPA you have to do a little configuration.
Click to Expand
OpenJPA does not correctly resolve SQL statements dependencies because it has a bug. OpenJPA by default ignores foreign keys between entity tables. You have two options to make it run.

First, you can use non-standard annotation ForeignKey. This would make the application non-portable.

Second, you can configure OpenJPA to read all foreign keys from the database. Place the openjpa.jdbc.SchemaFactory property with native(ForeignKeys=true) value into your persistence.xml:
<persistence-unit name="Cascading" transaction-type="RESOURCE_LOCAL">
 ...
 <properties>
  <!-- load foreign key information from the database -->
  <property name="openjpa.jdbc.SchemaFactory" value="native(ForeignKeys=true)"/> 
 </properties>
</persistence-unit>

Deleted Entities
If you delete an entity, you have to delete it from all relationships. JPA specification says little about what should happen if you forget to remove it from some of them.

Of course, if the owner is deleted, its relationship is deleted too. However, if the inverse is deleted, JPA behavior is undefined. Depending on the relationship configuration, the operation either throws an exception or correctly removes both entity and its relationships. What is even worst, if you use legacy database with missing foreign keys, you may end up with an inconsistent database.

The best is to keep relationships consistent. If you remove an entity, remove it also from all its relationships.

Merging New Entities
As we wrote before, you can use merge instead of persist to insert a new entity. This method has one caveat: the merge does not know what to do with a reference to a not-yet-merged entity. Instead of merging it, an exception is thrown:
Click to expand the test case:

@Test
public void impossibleMergeNoCascade() {
  // create a relationship between entities
  OneToOneOwner owner = new OneToOneOwner(11);
  OneToOneInverse inverse = new OneToOneInverse(11);
  owner.setInverse(inverse);
  inverse.setOwner(owner);

  // persist only the owner
  EntityManager em = getFactory().createEntityManager();
  em.getTransaction().begin();

  try {
    em.merge(owner);
  } catch (RuntimeException ex) {
    em.getTransaction().rollback();
  }

  // check whether previous catch closed the transaction
  assertFalse(em.getTransaction().isActive());

  em.getTransaction().begin();

  try {
    em.merge(inverse);
  } catch (RuntimeException ex) {
    em.getTransaction().rollback();
    em.close();
    return;
  }

  fail("It was supposed to throw an exception (twice).");
}

Fortunately, there is a simple workaround. It requires the cascading feature which will be described in the following chapter. In short, if you configure the cascade attribute of the relationship to CascadeType.MERGE, the merge operation will merge both entities at the same time.

Cascading
Unless configured otherwise, whatever you do to an entity happens only to that entity. If you delete an entity, only that entity is deleted. Both entities referenced by it and referencing it remain in database. The same applies for merge, persist, refresh or detach.

Each relationship can be configured to cascade a selection of these five operations. A cascading operation is applied first to the specified entity. Then it is applied to all referenced entities. If their relationships cascade too, the operation is applied to all entities referenced by them, and so on.

Cascading helps to solve a lot of relationship dependencies problems. Unfortunately, it is not able to solve all of them and may have some unintended consequences.

Configuration
Each relationship annotation has the cascade attribute. Use it to set a list of cascading operations. Allowed cascading operations are listed in the CascadeType enumeration. It has six members: PERSIST, MERGE, REMOVE, REFRESH, DETACH and ALL.

For example, following relationship cascades merge and persist operations on one side and all operations on the other side:
@Entity
public class CascadeOneToOneOwner {
  @OneToOne(cascade=CascadeType.ALL)
  private CascadeOneToOneInverse inverse;
}

@Entity
public class CascadeOneToOneInverse {
 @OneToOne(mappedBy="inverse", cascade={CascadeType.MERGE, CascadeType.PERSIST})
  private CascadeOneToOneOwner owner;
}

Limitation
Any cascade type can be used in combination with any relationship with one exception: the REMOVE can be used only with OneToOne and OneToMany relationships.
Click to collapse
Quote from the specification, page 41: The relationship modeling annotation constrains the use of the cascade=REMOVE specification. The cascade=REMOVE specification should only be applied to associations that are specified as OneToOne or OneToMany. Applications that apply cascade=REMOVE to other associations are not portable.
Merge Problem
Cascading can nicely solve the 'merge of new entities with relationship' problem. Recall the problem: if the merged entity references another not-yet-merged entity, the merge operation throws an exception.

However, the merge works if it cascades through the relationship. JPA implementation will merge both entities within one operation. It will also create a relationship between them.

Configure the cascade attribute of the relationship to CascadeType.MERGE and the merge will work:
Click to expand the test case:

@Test
public void mergeInverse() {
  // create a relationship between entities
  CascadeOneToOneOwner owner = new CascadeOneToOneOwner(10);
  CascadeOneToOneInverse inverse = new CascadeOneToOneInverse(10);
  owner.setInverse(inverse);
  inverse.setOwner(owner);

  // persist only the inverse
  EntityManager em = getFactory().createEntityManager();
  em.getTransaction().begin();
  em.merge(inverse);
  em.getTransaction().commit();
  em.close();

  // the merge operation was cascaded to the owner too
  // the owner was saved to the database
  assertEntityExists(CascadeOneToOneOwner.class, 10);
}

@Test
public void mergeOwner() {
  // create a relationship between entities
  CascadeOneToOneOwner owner = new CascadeOneToOneOwner(12);
  CascadeOneToOneInverse inverse = new CascadeOneToOneInverse(12);
  owner.setInverse(inverse);
  inverse.setOwner(owner);

  // persist only the owner
  EntityManager em = getFactory().createEntityManager();
  em.getTransaction().begin();
  em.merge(owner);
  em.getTransaction().commit();

  // the merge operation was cascaded to the inverse too
  // the inverse was saved to the database
  assertEntityExists(CascadeOneToOneInverse.class, 12);
}

Caution
It might be tempting to cascade all operations on all relationships. However, that is a bad idea for two reasons. First, that would badly affect the performance. Each operation would have potential to cascade to very large number of objects, potentially whole object graph.

The cascading of remove and persist operations applies also on those entities that have not been loaded yet. It even passes through them to other entities, potentially traversing through whole object graph.
Click to expand the test case:
The class CascadingTestCase located on Github contains two tests proving this claim.

Both tests assume that database contains a three etities: first, second and third. They reference each other via cascading relationships and all three have the same id.

Proof that delete cascades through unloaded entities:
@Test
public void cascadeDeleteThroughUnloaded() {
  // check the data: first, second and third entities are 
  // chained and have the same id
  checkRemoveChain(1);

  // remove first entity
  EntityManager em = getFactory().createEntityManager();
  em.getTransaction().begin();
  CascadeRemoveFirst first = em.find(CascadeRemoveFirst.class, 1);
  em.remove(first);
  em.getTransaction().commit();
  em.close();

  // both second and third entities have been removed
  // Note: neither one was loaded by the original entity manager
  assertEntityNOTExists(CascadeRemoveSecond.class, 1);
  assertEntityNOTExists(CascadeRemoveThird.class, 1);
}

A test proving that persist cascades through unloaded entities is named cascadePersistThroughUnloaded. It is available in the same class.
The cascading of refresh, merge and detach passes only through entities that are already loaded.
Click to expand the test case:
The class CascadingTestCase located on Github contains three tests proving this claim.

All three tests assume that database contains a three etities: first, second and third. They reference each other via cascading relationships and all three have the same id.

Proof that detach does not cascade through unloaded entities:
@Test
public void cascadeDetachThroughUnloaded() {
  // check the data: first, second and third entities are 
  // chained and have the same id
  checkChain(2);

  EntityManager em = getFactory().createEntityManager();
  // load both first and third element of chain
  CascadeFirst first = em.find(CascadeFirst.class, 2);
  CascadeThird third = em.find(CascadeThird.class, 2);
  // detach first element
  em.detach(first);
  // only first element was detached
  // detach operation does not cascade through unloaded elements
  assertFalse(em.contains(first));
  assertTrue(em.contains(third));
  em.close();
}

A tests proving that merge and refrest do not cascade through unloaded entities are named cascadeMergeThroughUnloaded and cascadeRefreshThroughUnloaded. Both are available in the same class.
Second, the operation might cascade to unintended entities. For example, you might remove one entity and persist another. If the persist operation cascades too far, it will resurrect removed entity back to life.
Click to expand the test case:
Resurrect removed entity test:
@Test
public void ressurectDeletedEntity() {
  EntityManager em = getFactory().createEntityManager();
  em.getTransaction().begin();
  // load two related entities
  CascadeOneToOneOwner owner = em.find(CascadeOneToOneOwner.class, 2);
  CascadeOneToOneInverse inverse = owner.getInverse();
  //delete one of them
  em.remove(owner);
  //new entity demo references second one
  CascadePersistDemo demo = new CascadePersistDemo(2);
  demo.setInverse(inverse);
  //persist the demo
  em.persist(demo);
  em.getTransaction().commit();
  em.close();
    
  //the persist operation cascaded through the 'inverse'
  //to the removed 'owner'. The owner was not deleted.
  assertEntityExists(CascadeOneToOneOwner.class, 2);
}

Configuration Overview
Each relationship property must be marked by one of @OneToOne, @OneToMany, @ManyToOne and @ManyToMany annotations. Each of these four annotations define defaults for underlying database structure, so theoretically no other configuration is needed.

Of course, database columns, tables and foreign keys are configurable. If you are not satisfied with defaults, use one of @JoinColumn, @JoinColumns, @JoinTable, @PrimaryKeyJoinColumn and @PrimaryKeyJoinColumns annotations to customize. These configuration annotations are allowed only on the 'owning side'. If you try to use them on the 'inverse side', JPA will throw an exception.

Each sub-chapter of this chapter contain short overview on one of relationship annotations. The overview always describes the relationship type and contains an example. All other details are explained in following chapters.

OneToOne
Each annotated entity references only one entity. Each opposite side entity is referenced by one owning entity.

Example: A person may have only one Facebook account and each Facebook account is owned by one person.
Click to collapse

@Entity
public class FacebookAccount {
  @OneToOne
  private Person owner;
}

@Entity
public class Person {
  @OneToOne(mappedBy="owner")
  private FacebookAccount facebookAccount;
}

OneToMany
Each annotated entity can reference multiple entities. However, the opposite side entity may NOT be referenced by multiple entities.

Example: A person may have any number of Twitter accounts. However, each Twitter account is owned by one person.
Click to collapse

@Entity
public class TwitterAccount {
  @ManyToOne
  private Person owner;
}

@Entity
public class Person {
  @OneToMany(mappedBy="owner")
  private Collection<TwitterAccount> twitterAccounts;
}

ManyToOne
The opposite of one-to-many relationship. The annotated entity can reference only one entity. However, any opposite side entity may be referenced by multiple entities.

Example: Each Twitter account is owned by one person. However, a person may have any number of Twitter accounts.
Click to collapse

@Entity
public class TwitterAccount {
  @ManyToOne
  private Person owner;
}

@Entity
public class Person {
  @OneToMany(mappedBy="owner")
  private Collection<TwitterAccount> twitterAccounts;
}

ManyToMany
Each annotated entity references multiple entities. Each opposite side entity can be referenced by any number of annotated entities.

Example: Each person may follow any number of twitter accounts. Each twitter account may be followed by any number of people.
Click to collapse

@Entity
public class TwitterAccount {
  @ManyToMany
  private Set<Person> followers;
}

@Entity
public class Person {
  @ManyToMany(mappedBy="followers")
  @MapKey(name="accountName")
  private Map<String, TwitterAccount> twitterFollowing;
}

One-To-One
Two entity classes have one-to-one relationship if each entity on one side corresponds to one entity on the other side.

For example, a person and its Facebook account have one-to-one relationship. The person may have only one Facebook account and each Facebook account is owned by one person.

Basic Configuration
To create bidirectional relationship, place the @OneToOne annotation on both sides of the relationship. Decide where you want to keep the joining column. That side is going to be the owner.

The inverse side must specify the mappedBy annotation attribute. If it is missing, JPA treats the relationship as two separate unidirectional relationships. The attribute contains the name of the owners property that keeps the relationship.

The owner side can not have the mappedBy attribute specified.
@Entity
public class OneToOneOwner {
  @OneToOne
  private OneToOneInverse inverse;
}

@Entity
public class OneToOneInverse {
  @OneToOne(mappedBy="inverse")
  private OneToOneOwner owner;
}

To create unidirectional relationship, place the @OneToOne annotation on the only side of the relationship. This side is going to be the owner.

Default Database Structure
Unless configured otherwise, JPA assumes that the owner table contains a column named PROPERTYNAME_ID. This column should contain references to the inverse side entity.

The previous relationship corresponds to following db structure:
Click to expand the test case:

CREATE TABLE onetooneowner (
  id INT NOT NULL, 
  inverse_id INT, 
  CONSTRAINT PK_ONETOONEOWNER PRIMARY KEY (id), UNIQUE (inverse_id)
);

CREATE TABLE onetooneinverse (
  id INT NOT NULL, 
  CONSTRAINT PK_ONETOONEINVERSE PRIMARY KEY (id)
);

ALTER TABLE onetooneowner 
ADD CONSTRAINT fk_onetooneownerinverse FOREIGN KEY (inverse_id) 
REFERENCES onetooneinverse (id);

Optional
If the annotation attribute optional is set to false, the relationship is mandatory. The entity must reference another entity. Relationship property can not be null.

If you try to persist an entity with empty mandatory property, JPA will throw an undocumented runtime exception.
Click to expand the test case:

@Test
public void mandatoryOneToOne_inverse() {
  EntityManager em = factory.createEntityManager();
  em.getTransaction().begin();
    
  LazyOneToOneInverse inverse = new LazyOneToOneInverse(10);
  try {
    em.persist(inverse);
    em.getTransaction().commit();
  } catch (RuntimeException ex) {
    em.close();
    return ;
  }
    
  fail("The transaction was supposed to fail.");
}

Owner with mandatory relationship is tested in similar test named mandatoryOneToOne_owner. It is defined in OneToOneTestCase test case available on Github.
Loading
One-to-one relationship is eagerly loaded. Lazy loading is possible only on the owner side and only if the relationship is not optional, e.g. the optional annotation attribute is set to false.

Lazily loading owner:
@Entity
public class LazyOneToOneOwner {
  @OneToOne(optional=false,fetch=FetchType.LAZY)
  private LazyOneToOneInverse inverse;
}

Test verifying the owner:
Click to expand the test case:

@Test
public void lazyOneToOne_owner() {
  EntityManager em = factory.createEntityManager();
    
  LazyOneToOneOwner owner = em.find(LazyOneToOneOwner.class, 1);
  em.close();
    
  //The relation ship is lazily loaded and the manager is already closed
  assertNull(owner.getInverse());
}

The inverse side was set to lazy too:
public class LazyOneToOneInverse {
  //lazy loading on the inverse side DOES NOT WORK
  //the parameter fetch=FetchType.LAZY is useless here
  @OneToOne(mappedBy="inverse",optional=false,fetch=FetchType.LAZY)
  private LazyOneToOneOwner owner;
}

This side of the relationship is still eagerly loaded:
Click to expand the test case:

@Test
  public void lazyOneToOne_inverse() {
    EntityManager em = factory.createEntityManager();
    
    OneToOneInverse owner = em.find(OneToOneInverse.class, 5);
    em.close();
    
    //lazy loading is possible only on the owner side
    assertEquals(1, owner.getOwner().getId());
  }

Cascading
All cascading types are compatible with one-to-one relationship.

Consistency
If the relationship is bidirectional, you are responsible for keeping it consistent. The safe way how to do it, is to let setters keep their own consistency.
Click to expand the test case:
One possible way how to write safe setters is available in org.meri.jpa.relationships.entities.bestpractice package in our demo project. Related test case is in class SafeRelationshipsTestCase.

@Entity
public class Person {

  @OneToOne(mappedBy = "owner")
  private FacebookAccount facebookAccount;

  public void setFacebookAccount(FacebookAccount facebookAccount) {
    //prevent endless loop
    if (sameAsFormer(facebookAccount))
      return;
    //set new facebook account
    FacebookAccount oldAccount = this.facebookAccount;
    this.facebookAccount = facebookAccount;
    //remove from the old facebook account
    if (oldAccount!=null)
      oldAccount.setOwner(null);
    //set myself into new facebook account
    if (facebookAccount!=null)
      facebookAccount.setOwner(this);
  }

  private boolean sameAsFormer(SafeFacebookAccount newAccount) {
    return facebookAccount == null ? 
      newAccount == null : facebookAccount.equals(newAccount);
  }

}

@Entity
public class FacebookAccount {
  // ... analogique to the person ...
}

Custom Join Column
Use the annotation @JoinColumn if you wish to customize database column name. This annotation must be used on the owner side.

The following example uses a customcolumn database column instead of the default one:
@Entity
public class ColumnOneToOneOwner {
  @OneToOne
  @JoinColumn(name="customcolumn")
  private ColumnOneToOneInverse inverse;
}

@Entity
public class ColumnOneToOneInverse {
  @Id
  @Column(name="inverse_id")
  private long id;

  @OneToOne(mappedBy="inverse")
  private ColumnOneToOneOwner owner;
}
Note: the @JoinColumn annotation has referencedColumnName attribute. It is not needed in this context. JPA will use id column of the inverse entity, even if it was renamed.

Coresponding database structure:
Click to expand the test case:

CREATE TABLE onetooneowner (
  id INT NOT NULL, 
  customcolumn INT, 
  CONSTRAINT 
    PK_ONETOONEOWNER PRIMARY KEY (id), 
    UNIQUE (customcolumn)
);

CREATE TABLE onetooneinverse (
  inverse_id INT NOT NULL, 
  CONSTRAINT PK_ONETOONEINVERSE PRIMARY KEY (inverse_id)
);

ALTER TABLE onetooneowner 
ADD CONSTRAINT fk_onetooneownerinverse FOREIGN KEY (customcolumn) 
REFERENCES onetooneinverse (inverse_id);

Warning: the annotation @JoinColumn has also a table attribute. This attribute is incompatible with one-to-one relationship. Theoretically, it should contain the name of the table that contains the column. We tried it twice and each attempt resulted in a weird SQL when we tried to load an entity.
Click to expand the test case:
First attempt switched owner and inverse tables:
@JoinColumn(name="customcolumn", table="ColumnOneToOneInverse")
Resulting load owner SQL ignored the customcolumn column:
SELECT t1.id, t2.id 
FROM ColumnOneToOneOwner t0 
INNER JOIN ColumnOneToOneInverse t1 ON t0.INVERSE_ID = t1.id 
LEFT OUTER JOIN ColumnOneToOneOwner t2 ON t1.id = t2.INVERSE_ID

Second attempt introduced a new table 'bumbum':
@JoinColumn(name="customcolumn", table="bumbum")
JPA produced SQL that does full join on the owner table:
SELECT t1.id, t3.id 
FROM bumbum t0 
INNER JOIN ColumnOneToOneInverse t1 ON t0.INVERSE_ID = t1.id 
LEFT OUTER JOIN bumbum t2 ON t1.id = t2.INVERSE_ID
, ColumnOneToOneOwner t3 
WHERE t0.OWNER_ID = ?

Custom Join Table
It is not possible to implement one-to-one relationship between two entities with a joining table. First of all, it is neither a good design nor a good idea. If you have a legacy database or have no say over database structure, you will have to use other then one-to-one relationship type in your model.

It might be tempting to use either @JoinTable annotation or the table attribute of the @JoinColumn annotation. Neither one works.
Click to expand the test case:
According to JPA API, the @JoinTable annotation should be placed on the owning side of a many-to-many association, or in an unidirectional one-to-many association. One-to-one relationship is not listed. We tried it anyway, just to see whether the thing works.

The test method insertUpdateLoadTableOneToOne is available in the OneToOneTestCase class on Github.

Insert, update and load of the owner entity work correctly. However, load of the inverse entity does not work. It generates three selects and two of them perform full join of two tables:
// this is OK
SELECT t0.inverse_id FROM TableOneToOneInverse t0 WHERE t0.inverse_id = ?

//full join!!
SELECT t1.owner_id FROM TableOneToOneJoin t0, TableOneToOneOwner t1 
WHERE t0.join_inverse_id = ?

// another full join
SELECT t1.inverse_id, t3.owner_id 
FROM TableOneToOneJoin t0 
INNER JOIN TableOneToOneInverse t1 ON t0.join_inverse_id = t1.inverse_id 
LEFT OUTER JOIN TableOneToOneJoin t2 ON t1.inverse_id = t2.join_inverse_id, TableOneToOneOwner t3 
WHERE t0.join_owner_id = ?

Join Trough Primary Key
Finally, it is possible to join entities through their primary keys. There is no special join column, entities are in relationship if their primary keys are equal. Place @PrimaryKeyJoinColumn annotation on the owner side:
@Entity
public class PrimaryOneToOneOwner {
  @OneToOne
  @PrimaryKeyJoinColumn
  private PrimaryOneToOneInverse inverse;
}

@Entity
public class PrimaryOneToOneInverse {
  @OneToOne(mappedBy="inverse")
  private PrimaryOneToOneOwner owner;
}

Corresponding database structure:
Click to expand the test case:

CREATE TABLE primaryonetooneowner (
  id INT NOT NULL, 
  CONSTRAINT PK_PRIMARYONETOONEOWNER PRIMARY KEY (id)
);

CREATE TABLE primaryonetooneinverse (
  id INT NOT NULL, 
  CONSTRAINT PK_PRIMARYONETOONEINVERSE PRIMARY KEY (id)
);

ALTER TABLE primaryonetooneowner 
ADD CONSTRAINT fk_primaryonetooneownerinverse FOREIGN KEY (id) 
REFERENCES primaryonetooneinverse (id);

Bidirectional One-To-Many / Many-To-One
One-to-many and many-to-one relationships are opposites of each other. If one side of the relationship is one-to-many, the other side must be many-to-one. Of course, the same goes the other way round. The opposite of many-to-one must be one-to-many.

The one-to-many side is able to reference any number of entities and keeps them in collection or in map. However, each instance in the many-to-one side can reference only one entity.

For example, a person and its Twitter accounts have this type of relationship. The person may have any number of Twitter accounts. Each Twitter account is owned by one person.

Basic Configuration
The property referencing multiple entities must be a Collection, a List, a Set or a Map. No other type is allowed. Mark it with @OneToMany annotation. This is the inverse side and must specify the mappedBy annotation attribute.

The side referencing only one entity is the owner and must be annotated with the @ManyToOne annotation.

If the inverse side property is either a List, a Set or a Collection, nothing else is needed.
@Entity
public class CollectionOwner {
  @ManyToOne
  private CollectionInverse inverse;
}

@Entity
public class CollectionInverse {
  @OneToMany(mappedBy="inverse")
  private Collection<CollectionOwner> owners;
}

If the inverse side property is a Map, the owner must have a property which contains a map key. Place also @MapKey annotation on the map. Annotation attribute name must contain the name of the property with a key:
@Entity
public class MapOwner {
  @Id
  private long id;
  private String mapKey;
  @ManyToOne
  private MapInverse inverse;
}

@Entity
public class MapInverse {
  @OneToMany(mappedBy="inverse")
  @MapKey(name="mapKey")
  private Map<String, MapOwner> owners;
}

The property specified in @MapKey will be used as a key to the map:
Click to collapse

@Test
public void basicMap() {
  EntityManager em = factory.createEntityManager();
    
  MapOwner owner = em.find(MapOwner.class, 1);
  MapInverse inverse = owner.getInverse();

  // mapKey property is a key to the map
  Map<String, MapOwner> owners = inverse.getOwners();
  assertEquals(owner, owners.get(owner.getMapKey()));
    
  em.close();
}

Default Database Structure
Unless configured otherwise, JPA assumes that the owners table, e.g. the one with entities on ManyToOne side, contains a column named PROPERTYNAME_ID. This column should contain references to the inverse side entity.

If a Map is used, the owner table must contain also a column corresponding to the map key property. The map key property should be unique.

Db structure for a version with a collection:
Click to expand the test case:

CREATE TABLE collectionowner (
  id INT NOT NULL, 
  inverse_id INT, 
  CONSTRAINT PK_COLLECTIONOWNER PRIMARY KEY (id)
);

CREATE TABLE collectioninverse (
  id INT NOT NULL, 
  CONSTRAINT PK_COLLECTIONINVERSE PRIMARY KEY (id)
);

ALTER TABLE collectionowner 
ADD CONSTRAINT fk_collectionownerinverse FOREIGN KEY (inverse_id) 
REFERENCES collectioninverse (id);

Db structure for a version with a map:
Click to expand the test case:

CREATE TABLE mapowner (
  id INT NOT NULL, 
  mapkey VARCHAR(1500) NOT NULL,
  inverse_id INT, 
  CONSTRAINT PK_MAPOWNER PRIMARY KEY (id), 
  UNIQUE (mapkey)
);

CREATE TABLE mapinverse (
  id INT NOT NULL, 
  CONSTRAINT PK_MAPINVERSE PRIMARY KEY (id)
);

ALTER TABLE mapowner 
ADD CONSTRAINT fk_mapownerinverse FOREIGN KEY (inverse_id) 
REFERENCES mapinverse (id);

Loading
The owner, e.g. @ManyToOne side of the relationship, is eagerly loading by default.
Click to expand the test case:

@Test
public void eagerLoadingCollection() {
  EntityManager em = factory.createEntityManager();
    
  CollectionOwner owner = em.find(CollectionOwner.class, 1);
  em.close();

  //this side is eagerly loaded by default
  CollectionInverse inverse = owner.getInverse();
  assertEquals(5, inverse.getId());
}

Unlike in one-to-one relationship, it is possible to configure it to be lazy:
@ManyToOne(fetch=FetchType.LAZY)
private LazyInverse inverse;
Click to expand the test case:

@Test
public void lazyLoadingCollection() {
  EntityManager em = factory.createEntityManager();
    
  LazyOwner owner = em.find(LazyOwner.class, 1);
  em.close();

  // it is possible to configure it to be lazy
  assertNull(owner.getInverse());
}

Cascading
All cascading types are compatible with one-to-many side of the relationship. The opposite many-to-one side does not support the REMOVE cascade type. It supports only PERSIST, MERGE, REFRESH and DETACH.

Consistency
You are responsible for keeping the relationship consistent. The safe way how to do it, is to hide the collection or map and expose only safe consistency keeping methods.
Click to expand the test case:
One possible way how to write safe accessor methods is available in org.meri.jpa.relationships.entities.bestpractice package in our demo project. Related test case is named SafeRelationshipsTestCase.

The example with a collection:
@Entity
public class Person {

  @OneToMany(mappedBy = "owner")
  private Collection<TwitterAccount> twitterAccounts = 
    new ArrayList<SafeTwitterAccount>();

  /**
   * Returns a collection with owned twitter accounts. The 
   * returned collection is a defensive copy.
   *  
   * @return a collection with owned twitter accounts
   */
  public Collection<TwitterAccount> getTwitterAccounts() {
    //defensive copy, nobody will be able to change 
    //the list from the outside
    return new ArrayList<TwitterAccount>(twitterAccounts);
  }

  /**
   * Add new account to the person. The method keeps 
   * relationships consistency:
   * * this person is set as the account owner
   */
  public void addTwitterAccount(TwitterAccount account) {
    //prevent endless loop
    if (twitterAccounts.contains(account))
      return ;
    //add new account
    twitterAccounts.add(account);
    //set myself into the twitter account
    account.setOwner(this);
  }
  
  /**
   * Removes the account from the person. The method keeps 
   * relationships consistency:
   * * the account will no longer reference this person as its owner
   */
  public void removeTwitterAccount(TwitterAccount account) {
    //prevent endless loop
    if (!twitterAccounts.contains(account))
      return ;
    //remove the account
    twitterAccounts.remove(account);
    //remove myself from the twitter account
    account.setOwner(null);
  }

}

@Entity 
public class FacebookAccount {
  /**
   * Set new twitter account owner. The method keeps 
   * relationships consistency:
   * * this account is removed from the previous owner
   * * this account is added to next owner
   * 
   * @param owner
   */
  public void setOwner(SafePerson owner) {
    //prevent endless loop
    if (sameAsFormer(owner))
      return ;
    //set new owner
    SafePerson oldOwner = this.owner;
    this.owner = owner;
    //remove from the old owner
    if (oldOwner!=null)
      oldOwner.removeTwitterAccount(this);
    //set myself to new owner
    if (owner!=null)
      owner.addTwitterAccount(this);
  }

  private boolean sameAsFormer(SafePerson newOwner) {
    return owner==null? newOwner == null : owner.equals(newOwner);
  }

}

Additionally, if you used a map, be careful about changing the value of the map key property.
Click to collapse
The next example shows one possible way how to write safe map key setter. It assumes that the opposite entity keeps twitter accounts in a map instead of collection. The accountName is the key to the map.
public void setAccountName(String accountName) {
  SafePerson myOwner = owner;
  myOwner.removeTwitterAccount(this);
  this.accountName = accountName;
  myOwner.addTwitterAccount(this);
}

This method has one drawback: depending on the JPA implementation, it may cause unnecessary database calls.
Optional
The @ManyToOne annotation has the attribute optional. If it is set to false, the relationship is mandatory. The entity must reference another entity. Relationship property can not be null.

If you try to persist an entity with empty mandatory property, JPA will throw an undocumented runtime exception.

The opposite @OneToMany annotation does not have such attribute.

Orphan Removal
An orphan in a bidirectional one-to-many many-to-one relationship is an owner entity that does not belong to the collection or map on the inverse side. In other words, an orphan is entity on the many-to-one side that does not reference an entity on the one-to-many side.

If the orphanRemoval attribute of @OneToMany annotation is set to true, JPA will automatically delete orphans. If you remove an entity from the annotated collection or map, JPA will delete it from the database.

Set orphan removal:
@OneToMany(mappedBy="inverse", orphanRemoval=true)
@MapKey(name="mapKey")
private Map<String, OrphanOwner> owners;

Delete the 'first' entity:
// load the inverse
OrphanInverse inverse = em.find(OrphanInverse.class, 5);
// remove the 'first' owner
inverse.getOwners().remove("first");
// commit the transaction and close manager
em.getTransaction().commit();

Expand to see full orphan removal test:
Click to expand the test case:

@Test
public void orphanRemoval() {
  EntityManager em = factory.createEntityManager();
  em.getTransaction().begin();
  // load the inverse
  OrphanInverse inverse = em.find(OrphanInverse.class, 5);
  // check whether the owner exists
  assertEquals(1, inverse.getOwners().get("first").getId());
  // remove the owner
  inverse.getOwners().remove("first");
  // commit the transaction and close manager
  em.getTransaction().commit();
  em.close();
  
  // owner without inverse has been removed
  assertEntityNOTExists(OrphanOwner.class, 1);
}

Ordering
If the one-to-many side of the relationship is a list, it may be sorted with the @OrderBy annotation. If you put property name followed by ASC or DESC into its value, JPA will use it to sort loaded entities. If you need multiple sorting properties, separate them with comma ','.

Sort by the ordering property. Entities with equal ordering property are sorted by id:
@Entity
public class OrderedInverse {
  @Id
  private long id;

  @OneToMany(mappedBy="inverse")
  @OrderBy("ordering ASC, id DESC")
  private List<OrderedOwner> owners;
}

@Entity
public class OrderedOwner {
  @Id
  private long id;
  @Column(name="orderby")
  private long ordering;

  @ManyToOne
  private OrderedInverse inverse;
}

The test of ordering is available in OneToMany_ManyToOneTestCase test case.

Custom Columns
Use the annotation @JoinColumn if you wish to customize the join column name. Map key column name is customized using the @Column annotations.

Following example uses a customcolumn database column instead of the default one:
@Entity
public class CustomColumnCollectionOwner {
  private String mapKey;
  @ManyToOne
  @JoinColumn(name="customcolumn")
  private CustomColumnCollectionInverse inverse;
}

@Entity
public class CustomColumnCollectionInverse {
  @OneToMany(mappedBy="inverse")
  private Collection<CustomColumnCollectionOwner> owners;
}

Coresponding database structure:
Click to expand the test case:

CREATE TABLE customcolumncollectionowner (
  id INT NOT NULL, 
  customcolumn INT, 
  CONSTRAINT PK_CUSTOMCOLUMNCOLLECTIONOWNER PRIMARY KEY (id)
);

CREATE TABLE customcolumncollectioninverse (
  id INT NOT NULL, 
  CONSTRAINT PK_CUSTOMCOLUMNCOLLECTIONINVERSE PRIMARY KEY (id));

ALTER TABLE customcolumncollectionowner 
ADD CONSTRAINT fk_customcolumncollectionownerinverse FOREIGN KEY (customcolumn) 
REFERENCES customcolumncollectioninverse (id);

Next example uses a customcolumn and customMapKey database columns instead of default ones:
@Entity
public class CustomColumnMapOwner {
  @Column(name="customMapKey")
  private String mapKey;

  @ManyToOne
  @JoinColumn(name="customcolumn")
  private CustomColumnMapInverse inverse;
}

@Entity
public class CustomColumnMapInverse {
  @OneToMany(mappedBy="inverse")
  @MapKey(name="mapKey")
  private Map<String, CustomColumnMapOwner> owners;
}

Coresponding database structure:
Click to expand the test case:

CREATE TABLE customcolumnmapowner (
  id INT NOT NULL, 
  custommapkey VARCHAR(1500) NOT NULL, 
  customcolumn INT, 
  CONSTRAINT PK_CUSTOMCOLUMNMAPOWNER PRIMARY KEY (id), 
  UNIQUE (custommapkey)
);

CREATE TABLE customcolumnmapinverse (
  id INT NOT NULL, 
  CONSTRAINT PK_CUSTOMCOLUMNMAPINVERSE PRIMARY KEY (id)
);

ALTER TABLE customcolumnmapowner 
ADD CONSTRAINT fk_customcolumnmapownerinverse FOREIGN KEY (customcolumn) 
REFERENCES customcolumnmapinverse (id);

Note: the @JoinColumn annotation has referencedColumnName attribute. It is not needed in this context. JPA will use id column of the inverse entity.

Warning: the annotation @JoinColumn has also a table attribute. This attribute is incompatible with many-to-one relationship. Do not use it.

Custom Join Table
It is not possible to implement bidirectional one-to-many many-to-one relationship between two entities with a joining table. First of all, it is neither a good design nor a good idea. If you have a legacy database or have no say over database structure, you will have to use other relationship type in your model.

It might be tempting to use either @JoinTable annotation or the table attribute of the @JoinColumn annotation. Neither one works.

Unidirectional Many-To-One
Unidirectional many-to-one relationship is exactly the same as the bidirectional version. The only difference is, that the inverse side does not have references to its owners.

Unidirectional relationship:
@Entity
public class UnidirectionalManyToOneOwner {
  @ManyToOne
  private UnidirectionalManyToOneInverse inverse;
}

@Entity
public class UnidirectionalManyToOneInverse {
  // nothing special in here
}

Unidirectional One-To-Many
Annotated property in an unidirectional one-to-many relationship can reference any number of entities. It is the owner and may keep them either in a collection or in a map. Even through there is no reference back, each entity on the other side can be owned by only one entity.

Basic Configuration
The annotated property must be a Collection, a List, a Set or a Map. No other type is allowed. Mark it with @OneToMany annotation.

If a List, a Set or a Collection is used, nothing else is needed:
@Entity
public class OneToManyOwner {
  @OneToMany
  private Collection<onetomanyinverse> inverses;
}

@Entity
public class OneToManyInverse {
  // nothing special in here
}

If a Map is used, the other side must have a property which contains a map key. Place also @MapKey annotation on the map. Annotation attribute name must contain the name of the property with a key:
@Entity
public class MapOneToManyOwner {
  @OneToMany
  @MapKey(name="mapKey")
  private Map<String, MapOneToManyInverse> owners;
}

@Entity
public class MapOneToManyInverse {
  private String mapKey;
}

The property specified in @MapKey will be used as a key to the map, the same way as in bidirectional version:
Click to collapse

@Test
public void basicMap() {
  EntityManager em = factory.createEntityManager();
    
  MapOneToManyOwner owner = em.find(MapOneToManyOwner.class, 1);
  MapOneToManyInverse inverse = em.find(MapOneToManyInverse.class, 5);
    
  // mapKey property is a key to the map
  Map<String, MapOneToManyInverse> inverses = owner.getInverses();
  assertEquals(inverse, inverses.get(inverse.getMapKey()));
    
  em.close();
}

Default Database Structure
Unless configured otherwise, JPA assumes that the relationship is kept in a table named OwnerTableName_InverseTableName.

The table should have two foreign key columns:
  • The column OwnerPropertyName_inversePrimaryKeyColumnName contains ids of inverse entities. It should have foreign key to the inverse table and have UNIQUE constraint on it.
  • The column OwnerClassName_ownerPrimaryKeyColumnName contains owner id and should have foreign key to the owners table.

If a Map is used, the inverse table must contain also a column corresponding to the map key property. The map key property should be unique.

Db structure for a version with a collection:
Click to collapse

OneToManyOwner (
  id INT NOT NULL, 
  CONSTRAINT PK_ONETOMANYOWNER PRIMARY KEY (id)
);

CREATE TABLE OneToManyInverse (
  id INT NOT NULL, 
  CONSTRAINT PK_ONETOMANYINVERSE PRIMARY KEY (id)
);

CREATE TABLE OneToManyOwner_OneToManyInverse (
  inverses_id INT NOT NULL, 
  OneToManyOwner_id INT NOT NULL, 
  UNIQUE (inverses_id)
);

ALTER TABLE OneToManyOwner_OneToManyInverse 
ADD CONSTRAINT fk_OneToManyInverse 
FOREIGN KEY (inverses_id) REFERENCES OneToManyInverse (id);

ALTER TABLE OneToManyOwner_OneToManyInverse 
ADD CONSTRAINT fk_OneToManyOwner
FOREIGN KEY (OneToManyOwner_id) REFERENCES OneToManyOwner (id);

Db structure for a version with a map:
Click to collapse

CREATE TABLE MapOneToManyOwner (
  id INT NOT NULL, 
  CONSTRAINT PK_MAPONETOMANYOWNER PRIMARY KEY (id)
);

CREATE TABLE MapOneToManyInverse (
  id INT NOT NULL, 
  mapkey VARCHAR(1500) NOT NULL, 
  CONSTRAINT PK_MAPONETOMANYINVERSE PRIMARY KEY (id),
  UNIQUE (mapkey)
);

CREATE TABLE MapOneToManyOwner_MapOneToManyInverse (
  MapOneToManyOwner_id INT NOT NULL, 
  inverses_id INT NOT NULL, 
  UNIQUE (inverses_id)
);

ALTER TABLE MapOneToManyOwner_MapOneToManyInverse 
ADD CONSTRAINT fk_MapOneToManyInverse 
FOREIGN KEY (inverses_id) REFERENCES MapOneToManyInverse (id);

ALTER TABLE MapOneToManyOwner_MapOneToManyInverse 
ADD CONSTRAINT fk_MapOneToManyOwner 
FOREIGN KEY (MapOneToManyOwner_id) REFERENCES MapOneToManyOwner (id);

Note: latest OpenJPA implementation (2.1.1) has a bug:
Click to collapse
The name of the column referencing the inverse is wrong. OpenJPA uses element_id as a default column name for the this column.
Loading
Unidirectional one-to-many relationship is lazy loading by default.

Cascading
All cascading types are compatible with unidirectional one-to-many relationship.

Consistency
Each inverse can be referenced by only one owner entity. You are responsible for keeping it this way. The safe way how to do it, is to set the inverse column in the joining table as UNIQUE. JPA will throw an exception if somebody tries to save incorrect data.

Additionally, if you used a map, be careful about changing the value of the map key property. As the inverse has no reference to the owner, it is not possible to write safe setter. Be aware of the issue.

Orphan Removal
An orphan in an unidirectional one-to-many relationship is an inverse entity that has no owner. It is the same thing as the orphan in the bidirectional version, only the sides changed.

If the orphanRemoval attribute of @OneToMany annotation is set to true, JPA will automatically delete orphans. In other words, if you remove the entity from the annotated collection or map, JPA will delete it from the database.

Set orphan removal:
@OneToMany(orphanRemoval=true)
private Collection<OrphanOneToManyInverse> inverses;

Delete all inverses referenced by the owner:
// load the owner and create orphan
OrphanOneToManyOwner owner = em.find(OrphanOneToManyOwner.class, 1);
owner.getInverses().clear();
// commit the transaction - referenced entities are removed
em.getTransaction().commit();

Expand to see full orphan removal test:
Click to expand the test case:

@Test
public void orphanRemovalOneToMany() {
  // check initial data - inverse is available
  assertEntityExists(OrphanOneToManyInverse.class, 5);

  EntityManager em = factory.createEntityManager();
  // load the owner and inverse
  OrphanOneToManyOwner owner;
  owner = em.find(OrphanOneToManyOwner.class, 1);
  OrphanOneToManyInverse inverse;
  inverse = em.find(OrphanOneToManyInverse.class, 5);

  // remove orphan
  em.getTransaction().begin();
  owner.getInverses().remove(inverse);
  // commit the transaction and close manager
  em.getTransaction().commit();
  em.close();
  
  // inverse without owner has been removed
  assertEntityNOTExists(OrphanOneToManyInverse.class, 5);
}

Custom Join Column
The @JoinColumn annotation is not compatible with the unidirectional one-to-many relationship. It may seem to work in some contexts, but it fails in others.
Click to expand the test case:
We used the @JoinColumn annotation to annotate the relationship between the ColumnOneToManyOwner and the ColumnOneToManyInverse tables. Following test shows situation, where such configuration does not work. The test is available in the OneToMany_ManyToOneTestCase test case:
@Test
public void insertUpdateLoadColumnOneToManyOwner() {
  //WARNING: this test may behave unpredictably
  ColumnOneToManyOwner owner = new ColumnOneToManyOwner(10);
  insert(owner);

  ColumnOneToManyInverse inverse = new ColumnOneToManyInverse(9);
  insert(inverse);

  //reload owner and inverse with another entity manager
  EntityManager em = factory.createEntityManager();
  inverse = em.find(ColumnOneToManyInverse.class, 9);
  owner = em.find(ColumnOneToManyOwner.class, 10);
  em.close();

  //create reference between owner and inverse and save them
  owner.setInverses(Arrays.asList(inverse));
  mergeAndCommit(inverse);
  mergeAndCommit(owner);

  //load the owner again 
  em = factory.createEntityManager();
  owner = em.find(ColumnOneToManyOwner.class, 10);
    
  //if the reference would be saved, the owner would have 1 inverse
  assertEquals(0, owner.getInverses().size());
  em.close();
}

If you wish to customize joining table columns, use the @JoinTable annotation.

Custom Join Table
The joining table can be customized with @JoinTable annotation. You can customize three attributes:
  • name - table name,
  • joinColumns - column containing owner id,
  • inverseJoinColumns - column containing inverse id.

If you leave some attribute unspecified, JPA will assume the default name.

Example configuration:
@Entity
public class TableMtmOwner {
  private String name;
  @ManyToMany
  @JoinTable(name="TableMtmJoin", 
    joinColumns=@JoinColumn(name="owner"), 
    inverseJoinColumns=@JoinColumn(name="inverse")
  )
  private Collection<TableMtmInverse> inverses;
}

@Entity
public class TableMtmInverse {
  @ManyToMany(mappedBy="inverses")
  @MapKey(name="name")
  private Map<String, TableMtmOwner> owners;
}

This works even if you have customized entity id columns or entity table names. The referencedColumnName and table attributes are not necessary:
Click to expand the test case:

@Entity
public class TableOneToManyOwner {
  @Id
  @Column(name="owner_id")
  private long id;

  @OneToMany
  @JoinTable(name="OneToManyJoinTable",
    joinColumns=@JoinColumn(name="owner"),
    inverseJoinColumns=@JoinColumn(name="inverse")
  )
  private Collection<TableOneToManyInverse> inverses;
}

@Entity
public class TableOneToManyInverse {
  @Id
  @Column(name="inverse_id")
  private long id;
}

Many-To-Many
Two entity classes have many-to-many relationship if each side is able to reference any number of entities and keeps them in a collection or in a map.

For example, twitter accounts and their followers have many-to-many relationship. Each account can be followed by any number of people and each person can follow any number of twitter accounts.

Basic Configuration
Place the @ManyToMany annotation on both sides of the relationship. The annotated property must be a Collection, a List, a Set or a Map. No other type is allowed.

Decide which side is going to be the owner and which side will be the inverse. You may pick sides at random, many-to-many relationship is symmetric.

The inverse side must specify the mappedBy annotation attribute. If it is missing, JPA treats the relationship as two separate unidirectional relationships. The attribute contains the name of the owners property that keeps the relationship. The owner side can not have the mappedBy attribute specified.

If the property is either a List, a Set or a Collection, nothing else is needed. If the property is a Map, you have to use the @MapKey annotation to specify the map key property:
@Entity
public class MtmOwner{
  private String name;
  @ManyToMany
  private Collection<MtmInverse> inverses = new ArrayList<MtmInverse>();
}

@Entity
public class MtmInverse{
  @ManyToMany(mappedBy="inverses")
  @MapKey(name="name")
  private Map<String, MtmOwner> owners = new HashMap<String, MtmOwner>();
}

The property specified in @MapKey will be used as a key to the map, the same way as in bidirectional one-to-many version:
Click to collapse

@Test
public void basicMap() {
  EntityManager em = factory.createEntityManager();
  
  MtmOwner owner = em.find(MtmOwner.class, 1);
  MtmInverse inverse = em.find(MtmInverse.class, 5);
    
  // mapKey property is a key to the map
  Map<String, MtmOwner> owners = inverse.getOwners();
  assertEquals(owner, owners.get(owner.getName()));
   
  em.close();
}

Default Database Structure
Unless configured otherwise, JPA assumes that the relationship is kept in a table named OwnerTableName_InverseTableName.

The table should have two foreign key columns:
  • the column OwnerPropertyName_inversePrimaryKeyColumnName contains ids of inverse entities and should have foreign key to the inverse table,
  • the column InversePropertyName_ownerPrimaryKeyColumnName contains owner id and should have foreign key to the owners table.

If a Map is used, the owner the corresponding map key property should be unique.

The previous relationship corresponds to following db structure:
Click to collapse

CREATE TABLE MtmOwner (
    id INT NOT NULL, name VARCHAR(1500), 
    name VARCHAR(1500) NOT NULL,
    CONSTRAINT PK_MTMOWNER PRIMARY KEY (id),
    UNIQUE (name)
);

CREATE TABLE MtmInverse (
    id INT NOT NULL, 
    CONSTRAINT PK_MTMINVERSE PRIMARY KEY (id)
);

CREATE TABLE MtmOwner_MtmInverse (
    inverses_id INT NOT NULL, 
    owners_id INT NOT NULL
);

ALTER TABLE MtmOwner_MtmInverse
ADD CONSTRAINT fk_MtmInverse 
FOREIGN KEY (inverses_id) REFERENCES MtmInverse (id);

ALTER TABLE MtmOwner_MtmInverse 
ADD CONSTRAINT fk_MtmOwner
FOREIGN KEY (owners_id) REFERENCES MtmOwner (id);

Note: latest OpenJPA implementation (2.1.1) has a bug:
Click to collapse
The name of the column referencing the owner is wrong. OpenJPA uses OwnerEntityName_ownerPrimaryKeyColumnName as a default column name for the this column.
Cascading
The REMOVE cascading type is not compatible with many-to-many relationship. It supports only PERSIST, MERGE, REFRESH and DETACH.

Loading
Many-to-many relationship is lazy loading by default.

Consistency
If the relationship is bidirectional, you are responsible for keeping it consistent. The safe way how to do it, is to hide the collection or map and expose only safe consistency keeping 'add', 'remove', ... methods.
Click to collapse
One possible way how to write safe accessor methods is available in org.meri.jpa.relationships.entities.bestpractice package in our demo project. Related test case is named SafeRelationshipsTestCase.
/**
 * Returns a collection with followed twitter accounts. The 
 * returned collection is a defensive copy.
 *  
 * @return a collection with followed twitter accounts
 */
public Map<String, SafeTwitterAccount> getTwitterFollowing() {
  //defensive copy, nobody will be able to change the 
  //list from the outside
  return new HashMap<String, SafeTwitterAccount>(twitterFollowing);
}

/**
 * Add new account to the list of followed twitter accounts. 
 * The method keeps relationships consistency:
 * * this person is set as the account follower also at the twitter side
 */
public void startFollowingTwitter(SafeTwitterAccount account) {
  //prevent endless loop
  if (twitterFollowing.containsValue(account))
    return ;
  //add new account
  twitterFollowing.put(account.getAccountName(), account);
  //set myself into the twitter account
  account.addFollower(this);
}

/**
 * Removes the account from the list of followed twitter 
 * accounts. The method keeps relationships consistency:
 * * this person is removed from the account followers 
 *   also at the twitter side
 */
public void stopFollowingTwitter(SafeTwitterAccount account) {
  //prevent endless loop
  if (!twitterFollowing.containsValue(account))
    return ;
  //remove the account
  twitterFollowing.remove(account.getAccountName());
  //remove myself from the twitter account
  account.removeFollower(this);
}

Additionally, if you used a map, be careful about changing the value of the map key property.
Click to collapse
One possible way how to write safe accessor methods is available in org.meri.jpa.relationships.entities.bestpractice package on Github. Related test case is named SafeRelationshipsTestCase.
public void setAccountName(String accountName) {
    Set<SafePerson> allFollowers = 
      new HashSet<SafePerson>(followers);

    for (SafePerson follower : allFollowers) {
      follower.stopFollowingTwitter(this);
    }
    
    this.accountName = accountName;

    for (SafePerson follower : allFollowers) {
      follower.startFollowingTwitter(this);
    }
  }

This method has one drawback: depending on the JPA implementation, it may cause unnecessary database calls.
Ordering
The ordering works exactly the same way as in one-to-many relationship.

If the relationship uses a list, it may be sorted with the @OrderBy annotation. Put property name followed by ASC or DESC into its value and JPA will use it to sort loaded entities. If you need multiple sorting properties, separate them with comma ','.

Sort by the ordering property. Entities with equal ordering property are sorted by id:
@Entity
public class OrderedMtmOwner {
  @ManyToMany
  @OrderBy("ordering ASC, id DESC")
  private List<OrderedMtmInverse> inverses = 
    new ArrayList<OrderedMtmInverse>();
}

@Entity
public class OrderedMtmInverse {
  @Id
  private long id;
  private String ordering;

  @ManyToMany(mappedBy="inverses")
  @MapKey(name="name")
  private Mapt<String, OrderedMtmOwnert> owners = 
    new HashMapt<String, OrderedMtmOwner>();
}

Custom Join Column
The @JoinColumn annotation is not compatible with the many-to-many relationship. If you wish to customize joining table columns, use the @JoinTable annotation.

Custom Join Table
The joining table can be customized with @JoinTable annotation, exactly the same way as in unidirectional one-to-many relationship. You can customize three attributes:
  • name - table name,
  • joinColumns - column containing owner id,
  • inverseJoinColumns - column containing inverse id.

If you specify only the name attribute, JPA will assume that joining columns have default names.

Example configuration:
@Entity
public class TableOneToManyOwner {
  @OneToMany
  @JoinTable(name="OneToManyJoinTable",
    joinColumns=@JoinColumn(name="owner"),
    inverseJoinColumns=@JoinColumn(name="inverse")
  )
  private Collection<TableOneToManyInverse> inverses;
}

@Entity
public class TableOneToManyInverse {
  // nothing special here
}

This works even if you have customized entity id columns or entity table names. The referencedColumnName and table attributes are not necessary.

End

This post covered only the most basic JPA features. Of course, JPA has much more worth learning about.

The list of features missing from this post includes:
  • JPQL Query Language,
  • class inheritance,
  • automatic entity id generation,
  • custom data types,
  • entities composed of multiple tables,
  • metadata,
  • configuration via xml instead of annotations,
  • entity lifecycle callbacks.

Some of them are more useful than others. While JPQL and class inheritance are a must, metadata and entity lifecycle callbacks are useful only for some projects. The rest is somewhere in between.

217 comments:

«Oldest   ‹Older   1 – 200 of 217   Newer›   Newest»
Javin said...

Nice and quite Comprehensive post. Thanks for putting effort and sharing information.

Javin
Quick tip on LDAP Authentication using Spring Security

Meri said...

Hi Javin,

thank you. I'm glad to hear that someone liked it.

Meri

Anonymous said...

Your post is very good. Previously I have been working with XML, but now it's time to move to Database.

Ilias Tsagklis said...

Hi Maria,

Nice blog! Is there an email address I can contact you in private?

Maruf Hassan said...

very very nice post and thank you very much for your easy explanations...

mi said...

Thanks, nice and short to the point.

A couple of things worth pointing out:
1) JPA spec recommends @OneToMany together with @JoinTable as opposed to @JoinColumn (so that the child table does not end up with a foreign key, lending itself to different types of parents), and
2) Hibernate has a bug that is worth noting if you model it like in 1) above. See Hibernate Jira https://hibernate.onjira.com/browse/HHH-1268

Anonymous said...

This is great stuff, thanks so much!

Anonymous said...

Are the Relationship examples all correct?

Meri said...

@Anonymous As far as I know, they are all correct. Of course, if you found a bug, let me know and I will fix that.

TearsOfSilence said...

Thank you Sir! This is pretty much the best review of JPA I've found so far. Short and to the point.

g1 said...

can u enhance this article to include 'validation'?
validation of entity classes with the underlying database itself.
(PS: something like Hibernate Validator)

Meri said...

@Sriram Viswanathan Thank you.

Meri said...

@g1 That sounds like an interesting idea. I will have a look on it, but unless it turns out to be very small topic, I will not able to do it within short time (unfortunately). But I will definitely put it on my todo list.

natarajan said...

Excellent tutorial for clear understanding on JPAs
!

Unknown said...

I liked your discussion of Consistency most....
Perfect job, thank you.

Velmurugan said...

very nice post for beginners ,,, thank you so much for putting some effort ....

Anonymous said...

Nice work, congrats :-)

Anonymous said...

V nice post, to the point and as short as possible. Thanks.

sqvarek said...

Very comprehensive info on relations, props for explaining the gotchas :D

Thank you, Maria :)

Unknown said...

Very good tutorial - thank you very much

RuggedBrain said...

Thanks for documenting this. It was very useful.

Unknown said...

Thank you very much, very good tutorial

SEO BACKLINKS said...

SEE MORE ARTICLE IN HERE. CLICK IN HERE

Unknown said...


Thank you for sharing your article. Great efforts put it to find the list of articles which is very useful to know, Definitely will share the same to other forums.

best openstack training in chennai | openstack course fees in chennai | openstack certification in chennai | redhat openstack training in chennai

Ayushman srivastava said...

Helpful

IT Tutorials said...

Really useful information. Thank you so much for sharing.It will help everyone.Keep Post. RPA training in chennai | RPA training in Chennai with placement | UiPath training in Chennai | UiPath certification in Chennai with cost

Anonymous said...

Lampung
Samsung
youtube
youtube
lampung
kuota
Indonesia
lampung
lampung
youtube

Chiến SEOCAM said...

Phối chó bull pháp

Phối giống chó Corgi

Phối chó Pug

Phối giống chó alaska

DedicatedHosting4u said...

Just seen your Article, it amazed me and surpised me with god thoughts that eveyone will benefit from it. It is really a very informative post for all those budding entreprenuers planning to take advantage of post for business expansions. You always share such a wonderful articlewhich helps us to gain knowledge .Thanks for sharing such a wonderful article, It will be deinitely helpful and fruitful article.
Thanks
DedicatedHosting4u.com

shilpi said...

It is a very popular application which provides you to download videos, songs, mashups, remixes, trailers in your mobile , and off course in any quality. It contains many videos and of many types .
like funny videos, motivational videos, songs lyrics, trailer and many more.
Want to watch movie in slow connection,
Vidmate app,
Vidmate app to watch videos,
vidmate,
Vidmate Features,
Vidmate Download,
Multi sSource Downloader,
cm security antivirus,
leaked Movie Online,
Why do people prefer Vidmate App.

lajwantidevi said...

This is a nice Site to watch out for and we provided information on
vidmate make sure you can check it out and keep on visiting our Site.

kajal singh rajput said...

Download and install Vidmate App which is the best HD video downloader software available for Android. Get free latest HD movies, songs, and your favorite TV shows.

wemake said...

<a href="https://vidmate.vin/

Unknown said...

I really enjoyed your blog Thanks for sharing such an informative post.
https://myseokhazana.com/
https://seosagar.in/
Indian Bookmarking list
Indian Bookmarking list
India Classified Submission List
Indian Classified List
Indian Bookmarking list
Indian Bookmarking list
India Classified Submission List
Indian Classified List

gokul said...

Thank you for this informative blog
Top 5 Data science training in chennai
Data science training in chennai
Data science training in velachery
Data science training in OMR
Best Data science training in chennai
Data science training course content
Data science certification in chennai
Data science courses in chennai
Data science training institute in chennai
Data science online course
Data science with python training in chennai
Data science with R training in chennai

Vipin said...

Thanks for provide great informatic and looking beautiful blog, really nice required information & the things i never imagined and i would request, wright more blog and blog post like that for us. Thanks you once agianMarriage certificate in delhi
Marriage certificate in ghaziabad
Marriage registration in gurgaon
Marriage registration in noida
special marriage act
Marriage certificate online
Marriage certificate in mumbai
Marriage certificate in faridabad
Marriage certificate in bangalore
Marriage certificate in hyderabad thanks once again to all.

ajay said...

vidmate app

Vipin said...

Thanks for Fantasctic blog and its to much informatic which i never think ..Keep writing and grwoing your self

birth certificate in bangalore
name add in birth certificate
birth certificate in mumbai
birth certificate in faridabad
birth certificate in gurgaon
birth certificate in hyderabad
birth certificate online
birth certificate in noida
birth certificate in ghaziabad
birth certificate in delhi

unknow said...


I really enjoyed your blog Thanks for sharing such an informative post.
https://myseokhazana.com/
https://seosagar.in/
Indian Bookmarking list
Indian Bookmarking list
India Classified Submission List
Indian Classified List
Indian Bookmarking list
Indian Bookmarking list
India Classified Submission List
Indian Classified List

pat said...

vidmate

Anonymous said...

Nice Post thanks for the information, good information & very helpful for others,Thanks for Fantasctic blog and its to much informatic which i never think ..Keep writing and grwoing your self

duplicate rc in delhi online
duplicate rc in ghaziabad
duplicate rc in online
duplicate rc in greater noida
duplicate rc in mumbai
duplicate rc in bangalore
duplicate rc in faridabad
duplicate rc in gurgaon
duplicate rc in noida
death certificate online

pat said...

vidmate

pat said...

vidmate

raj khan said...

Soma pill is very effective as a painkiller that helps us to get effective relief from pain. This cannot cure pain. Yet when it is taken with proper rest, it can offer you effective relief from pain.
This painkiller can offer you relief from any kind of pain. But Soma 350 mg is best in treating acute pain. Acute pain is a type of short-term pain which is sharp in nature. Buy Soma 350 mg online to get relief from your acute pain.

https://globalonlinepills.com/product/soma-350-mg/


Buy Soma 350 mg
Soma Pill
Buy Soma 350 mg online



Buy Soma 350 mg online
Soma Pill
Buy Soma 350 mg

lajwantidevi said...

call adultxxx
call girl
xadult

Ozone said...

thanks for sharing this awesome content
top 10biographyhealth benefitsbank branchesoffices in Nigeriadangers ofranks inhealthtop 10biographyhealth benefitsbank branchesoffices in Nigerialatest newsranking biography

Ozone said...

thanks for sharing this awesome content
top 10biographyhealth benefitsbank branchesoffices in Nigeriadangers ofranks inhealthtop 10biographyhealth benefitsbank branchesoffices in Nigerialatest newsranking biography

mahi said...

Please refer below if you are looking for best project center in coimbatore

Java Training in Coimbatore | Digital Marketing Training in Coimbatore | SEO Training in Coimbatore | Tally Training in Coimbatore | Python Training In Coimbatore | Final Year IEEE Java Projects In Coimbatore | IEEE DOT NET PROJECTS IN COIMBATORE | Final Year IEEE Big Data Projects In Coimbatore | Final Year IEEE Python Projects In Coimbatore

Thank you for excellent article.

Rajesh said...

Nice infromation
Selenium Training In Chennai
Selenium course in chennai
Selenium Training
Selenium Training institute In Chennai
Best Selenium Training in chennai
Selenium Training In Chennai

Rajesh said...

Rpa Training in Chennai
Rpa Course in Chennai
Rpa training institute in Chennai
Best Rpa Course in Chennai
uipath Training in Chennai
Blue prism training in Chennai

Rajesh said...

Data Science Training In Chennai
Data Science Course In Chennai
Data Science Training institute In Chennai
Best Data Science Training In Chennai


Python Training In Chennai
Python course In Chennai
Protractor Training in Chennai
jmeter training in chennai
Loadrunner training in chennai

Unknown said...

i found this article more informative, thanks for sharing this article!
showbox
showbox for pc

Vipin Chauhan said...

Nice Post thanks for the information, good information & very helpful for others,Thanks for Fantasctic blog and its to much informatic which i never think ..Keep writing and grwoing your self

rc transfer in delhi
rc transfer in ghaziabad
rc transfer in noida
rc transfer in gurgaon
rc transfer in faridabad
rc transfer in bangalore
rc transfer in greater noida
rc transfer in mumbai
rc transfer in ranchi
rc transfer in chennai

James said...

It is essential for you to contact an expert to accomplish your design of the screen boxes. Every successful organization person today, knows the importance of applying good appearance material for his or her boxes.custom printed boxes wholesale, paper box with window, custom packaging boxes, the box wholesale, custom boxes miami, box packaging, custom gift boxes with logo, cheap custom boxes, custom printed gift box, find

lajwantidevi said...

vidmate

Reena said...

Thanks for provide great informatic and looking beautiful blog, really nice required information & the things i never imagined and i would request, wright more blog and blog post like that for us. Thanks you once agian

court marriage in delhi ncr
court marriage in delhi
court marriage in noida
court marriage in ghaziabad
court marriage in gurgaon
court marriage in faridabad
court marriage in greater noida
name change online
court marriage in chandigarh
court marriage in bangalore

dev said...


thanks for providing such a great article, this article is very help full for me, a lot of thanks sir
finance project for mba
finance topics for mba project
Ignou Synopsis

Venkatesh CS said...

Excellent Blog. Thank you so much for sharing.
Artificial Intelligence Training in Chennai
Best Artificial Intelligence Training in Chennai
artificial intelligence training institutes in Chennai
artificial intelligence certification training in Chennai
artificial intelligence course in Chennai
artificial intelligence training course in Chennai
artificial intelligence certification course in Chennai
artificial intelligence course in Chennai with placement
artificial intelligence course fees in chennai
best artificial intelligence course in Chennai
AI training in chennai
artificial intelligence training in omr
artificial intelligence training in Velachery
artificial intelligence course in omr
artificial intelligence course in Velachery

Sonam Gupta said...

I have saved this link and will return in a Income tax filingcouple of months, when I need to build my first blog. Thank you for the information.

DILIP KUMAR said...

Mind boggling article. Thankful for your remarkable information, the substance is extremely interesting. I will keep things under control for your next post.
satta result
satta king
satta chart
satta matka result
satta matka
satta king online Result
satta
gali satta
disawar result

Jack sparrow said...


That is nice article from you , this is informative stuff . Hope more articles from you . I also want to share some information about openstack online training and websphere portal tutorial

Buồn thế said...

ok anh hai

cửa lưới dạng xếp

cửa lưới chống muỗi

lưới chống chuột

cửa lưới chống muỗi hà nội

Unknown said...

What a Beautiful post! This is so chock full of useful information I can't wait to dig and start using my time on blogging. I am just over six month into blogging , I newly wrote an article Best Immune Booster Fitspire product makes immune stronger & healthier to fight against corona

Unknown said...

What a Beautiful post! This is so chock full of useful information I can't wait to dig and start using my time on blogging. I am just over six month into blogging , I newly wrote an article Best Immune Booster Fitspire product makes immune stronger & healthier to fight against corona

Priya said...

What a Beautiful post! This is so chock full of useful information I can't wait to dig and start using my time on blogging. I am just over six month into blogging , I newly wrote an article Best Immune Booster Fitspire product makes immune stronger & healthier to fight against corona.

Priya said...

What a Beautiful post! This is so chock full of useful information I can't wait to dig and start using my time on blogging. I am just over six month into blogging , I newly wrote an article Best Immune Booster Fitspire product makes immune stronger & healthier to fight against corona.

Bồn ngâm massage chân Doca said...

ok anh hi

https://bonngamchan.vn/may-massage-chan-hang-nhat-co-tot-nhu-loi-don/

https://bonngamchan.vn/

https://bonngamchan.vn/danh-muc/bon-ngam-chan/

https://bonngamchan.vn/may-ngam-chan/

Bồn ngâm massage chân Doca said...

ok anh

https://ngoctuyenpc.com/man-hinh-may-tinh-24-inch

https://ngoctuyenpc.com/mua-ban-may-tinh-cu-ha-noi

https://ngoctuyenpc.com/mua-ban-may-tinh-laptop-linh-kien-may-tinh-cu-gia-cao-tai-ha-noi

https://ngoctuyenpc.com/cay-may-tinh-cu

Malik said...

Really Nice Article & Thanks for sharing.

Oflox Is The Best Website Design & Development Company In Dehradun or Digital Marketing Company In Dehradun

Mr Frudo said...

poeple are really enjoying your content can you please post a content upon Mobile Phone Prices in Pakistan

balu said...

kunkumadi face Oil

B Best Hair Oil

wheatgrass powder

B on

Balu Herbals

self-'Judge(d)'....:-) said...

hi ,
thanks for the share.It cleared the air about JPA.Know i can say i am aware about the

Indhu said...

Thanks for sharing this informations.
artificial intelligence training in coimbatore

Blue prism training in coimbatore

RPA Course in coimbatore

C and C++ training in coimbatore

big data training in coimbatore

hadoop training in coimbatore

aws training in coimbatore



Chiến NHX said...

Những chia sẻ quá hay mà thú vị

Dịch vụ vận chuyển chó mèo cảnh Sài Gòn Hà Nội

Chuyên dịch vụ phối giống chó Corgi tại Hà Nội

Phối chó Bull Pháp

Vipin Chauhan said...

Thanks for provide great informatic and looking beautiful blog, really nice required information & the things i never imagined and i would request, wright more blog and blog post like that for us. Thanks you once agian

religion change
arya samaj mandir in ghaziabad
arya samaj mandir in greater bangalore
name change ads in newspaper
arya samaj mandir in punjab
arya samaj mandir in rajasthan
arya samaj mandir in noida
arya samaj mandir in gurgaon
arya samaj mandir in faridabad
arya samaj mandir in delhi

Venkatesh CS said...

Excellent Blog. Thank you so much for sharing.
salesforce training in chennai
salesforce training in omr
salesforce training in velachery
salesforce training and placement in chennai
salesforce course fee in chennai
salesforce course in chennai
salesforce certification in chennai
salesforce training institutes in chennai
salesforce training center in chennai
salesforce course in omr
salesforce course in velachery
best salesforce training institute in chennai
best salesforce training in chennai

Chiến SEOCAM said...

ok anh

máy xông tinh dầu phun sương bottle cap

may xong phong ngu

may xong huong

may phun suong nao tot

Anonymous said...

It was great to read your article. Read mine here
Mirakee
Framasphere
Mundoalbiceleste

phannhathoang144@gmail.com said...

Bài viết rất hay: Chúng tôi chuyên cung cấp các sản phẩm chất lượng



Lều xông hơi giá rẻ tại hà nội



Những ai có thể dùng lều xông hơi tại nhà



Tại sao nên xông hơi với tinh dầu sả

Suresh Dasari said...

Thanks for the article. Good one.

ramesh1313 said...
This comment has been removed by the author.
ramesh1313 said...

Nice article for android articles

pratheep said...

Excellent Blog!!! Such an interesting blog with clear vision, this will definitely help for beginner to make them update.

Robotic Process Automation (RPA) Training in Chennai | Robotic Process Automation (RPA) Training in anna nagar | Robotic Process Automation (RPA) Training in omr | Robotic Process Automation (RPA) Training in porur | Robotic Process Automation (RPA) Training in tambaram | Robotic Process Automation (RPA) Training in velachery

Miya said...

I am regular reader of your blog from long time,What a Beautiful post! This is so chock full of useful information I can’t wait to dig and start using my time on blogging,. I am just over six month into blogging , I newly wrote an article Dealer Distributor data Thanks for sharing it.

Phan Thương said...

Ngày nay, bàn học không chỉ có chức năng để học nữa mà còn như một vật trang trí trong phòng của các bé. Khác hẳn với bé gái, bàn học dành cho bé trai mang vẻ năng động và tinh nghịch. Vì vậy, hãy tham khảo những mẫu bàn học dành cho bé trai dưới đây để tìm ra mẫu phù hợp cho con mình nhé!

buy instagram followers india said...

nice post...
We offer 100% active and natural instagram followers from targeted location and worldwide region : Buy Active Instagram Followers India

Aditi Gupta said...

Such a great post. If you want to instagram followers to increase engagement and reach the target audience. Thanks for sharing your information. Buy Mumbai Instagram followers

Lokman said...

This is a great explanation, thank you so much!

ramesh1313 said...

Awesome read, keep up the great work! for more Tutorials

IT Technology Updates said...

Cool Blog..you have shared such an amazing content..Nice

Python Training in Vadapalani | Python Training in Chennai | Python Course in Chennai | Python Training in anna nagar | Python Training in tambaram | Python Training in Velachery | Python Training in omr | Python Training in madipakkam | Python Training in porur | Python Training in adayar

Bồn ngâm massage chân Doca said...

Hay mà

máy tính hà nội

màn hình máy tính

mua máy tính cũ

màn hình máy tính cũ

dev said...

Hi! I really like your contentYour post is really informative. .Ignou Project
.Ignou Synopsis

Nhathuoctot.net said...

Bài viết tuyệt vời

https://nhathuoctot.net/nhung-dieu-chua-biet-ve-giao-co-lam-9-la/

https://nhathuoctot.net/giao-co-lam-5-la-kho-hut-chan-khong-500gr/

https://nhathuoctot.net/giao-co-lam-hoa-binh/

https://nhathuoctot.net/cong-ty-ban-hat-methi-tot-tai-ha-noi/

Khá Buồn said...

anh đã có bài viết quá hay

lều xông hơi

lều xông hơi tại nhà

lều xông hơi giá rẻ

lều xông hơi sau sinh

Sages Marketing said...

http://gbasibe.com
http://gbasibe.com
http://gbasibe.com
http://gbasibe.com
http://gbasibe.com
http://gbasibe.com
http://gbasibe.com
http://gbasibe.com

Sages Marketing said...

http://gbasibe.com
http://gbasibe.com
http://gbasibe.com
http://gbasibe.com
http://gbasibe.com
http://gbasibe.com
http://gbasibe.com
http://gbasibe.com

fuel digital vignesh said...

This is very good post I have read and I must appreciate you to have written this for us.Its really informative.
Great article with excellent idea i appreciate your post thankyou so much and let keep on sharing your stuffs
Thanks for the article…
Best Digital Marketing Agency in Chennai
Best SEO Services in Chennai
seo specialist companies in chennai
Brand makers in chennai
Expert logo designers of chennai
Best seo analytics in chennai
leading digital marketing agencies in chennai
Best SEO Services in Chennai

phannhathoang144@gmail.com said...

Bài viết rất hay: Chúng tôi chuyên cung cấp các sản phẩm chất lượng



Giảo cổ lam giảm mỡ hiệu quả



Tìm hiểu giảo cổ lam khô hiện nay



Tác dụng thần kỳ của giảo cổ lam khô

phannhathoang144@gmail.com said...

Bài viết rất hay: Chúng tôi chuyên cung cấp các sản phẩm chất lượng



Lều xông hơi tại nhà giá rẻ



Lều xông hơi mua ở đâu



Lều xông hơi giá bao nhiêu

Sages Marketing said...

Very interesting blog. Many blogs I see these days do not really provide anything that attracts others, but believe me the way you interact is literally awesome.You can also check my articles as well.

Security Guard License
Ontario Security License
Security License Ontario
Security License

Thank you..

fox king said...


Really great post admin thanks for sharing this.
JioTV live for PC
Vivavideo for PC Download
Cartoon HD for PC Apk
Jio Fiber Register
Snapseed for PC
Whatsapp for laptop

Shivam said...
This comment has been removed by the author.
Shivam said...

IPL Toss Astrology

Today Match IPL Bhavishyavani

Vipin Chauhan said...


Thanks for provide great informatic and looking beautiful blog, really nice required information & the things i never imagined and i would like to request, wright more blog and blog post like that for us. Thanks you once agian

single status certificate in punjab
single status certificate in india
single status certificate in uttar pradesh
single status certificate in west bengal
single status certificate in bihar
single status certificate in maharashtra
single status certificate in haryana
single status certificate in kerala
single status certificate in rajasthan
single status certificate

beobeoyeu068@gmail.com said...

Bài viết rất hay: Chúng tôi chuyên cung cấp các sản phẩm chất lượng sau:



bồn massage giá rẻ tại hà nội



bồn mát xa có lợi ích gì



Cảm ơn các bạn!<>

Kathleen Brain said...

“I jumped on an opportunity to purchase a rental property over the 4th of  weekend. Mr Lee was quick to respond and since this was my first time getting a loan to buy a rental property , he was able to help me walk through the loan process. It was a great experience working with a good and kind loan lender Mr Lee. I hopefully know very well if you are outta looking for loan to purchase a property or funding business purpose then Mr Lee will be able to help you with such process here his details WhatsApp +1-989-394-3740.   /  247officedept@gmail.com  !”  

phannhathoang144@gmail.com said...

Bài viết rất hay: Chúng tôi chuyên cung cấp các sản phẩm chất lượng



Lều xông hơi tại nhà giá rẻ



Lều xông hơi mua ở đâu



Lều xông hơi giá bao nhiêu

phannhathoang144@gmail.com said...

Bài viết rất hay: Chúng tôi chuyên cung cấp các sản phẩm chất lượng



Giảo cổ lam giảm mỡ hiệu quả



Tìm hiểu giảo cổ lam khô hiện nay



Tác dụng thần kỳ của giảo cổ lam khô

Unknown said...

After reading your article I was amazed. I know that you explain it very well

Python Coaching Classes near me | Python Tutorial in coimbatore | python Training Institute in coimbatore| Best Python Training Centre | Online python Training Institute in coimbatore | Python Course with placement in coimbatore | Python Course training in coimbatore | Python training in saravanampatti

Unknown said...

Nice information,thanks for given this useful message really it was helpful

Web designing and development Training in Coimbatore | HTML Training in Coimbatore| React JS Training in coimbatore | Online React JS Training in coimbatore| Web Development Training in Coimbatore | Online Web Development Training in Coimbatore | Web Designing Course in Saravanampatti | Web Development Institute in Coimbatore | Web Designing Training in Saravanampatti | Online Web Designing Training in Saravanampatti

Unknown said...

Thank You for providing us with such an insightful information through this blog.
Digital Marketing Training in Coimbatore Digital Marketing Training Institute in Coimbatore | Digital Marketing Training Course in Coimbatore | Best Digital Marketing Training in Coimbatore | Digital Marketing Training in Saravanampatti | Digital Marketing Course in Coimbatore | Online Digital Marketing Training in Coimbatore

unknow said...

I really enjoyed your blog Thanks for sharing such an informative post.
https://www.login4ites.com/

Gaurav khambra said...

Thank you for sharing such an amazing content
Also you can visit my site and find similar and informative content on the topic :
best digital marketing training institute delhi noida


Nino Nurmadi , S.Kom said...

Nino Nurmadi, S.Kom
Nino Nurmadi, S.Kom
Nino Nurmadi, S.Kom
Nino Nurmadi, S.Kom
Nino Nurmadi, S.Kom
Nino Nurmadi, S.Kom
Nino Nurmadi, S.Kom
Nino Nurmadi, S.Kom
Nino Nurmadi, S.Kom

centre99 said...

it is really a great and helpful piece of info. I am glad that you shared this helpful information with us. Please keep us informed like this. Thank you for sharing.
malaysia visa in Singapore

tredmill said...

thanks for providing such a great article, this article is very help full for me, a lot of thanks sir
exercise cycle online
gym accessories shop near me

Fuel Digi said...

We are the Best Digital Marketing Agency in Chennai.Our team consists of very best of programmers,web developers,SEO experts and many others. we offer all digital marketing service at affordable cost.

Fuel Digi said...

We are the Best Digital Marketing Agency in Chennai.Our team consists of very best of programmers,web developers,SEO experts and many others. we offer all digital marketing service at affordable cost.
Thanks for the article…
Best SEO analytics in chennai
Website Designing Company in chennai
Social Media marketing

Shayari Sad said...

Life Status in Hindi
Status

Ramesh ji said...

We have seen many blog but this the great one, Thanks for provide great informatic and looking beautiful blog, really nice required information & the things i never imagined and i would like to request, wright more blog and blog post like that for us. Thanks you once agian

birth certificate in delhi
birth certificate in noida
birth certificate in ghaziabad
birth certificate in gurgaon
birth certificate agent in delhi
marriage registration in delhi
marriage certificate delhi
correction in 10th mark sheet
marriage registration in ghaziabad
marriage registration in gurgaon

Máy khuếch tán tinh dầu said...

Những chia sẻ quá hay

máy xông tinh dầu bằng điện

máy khuếch tán tinh dầu silent night

máy xông tinh dầu đuổi muỗi

máy khuếch tán tinh dầu hà nội

Shayari Sad said...

Happy New Year Wishes Shayari
Shayari

R ADK said...

Drilling consultants
edumeet | python training in chennai
Organic Chemistry tutor

FIFA WORLD CUP said...

Welcome !! An enthusiastic sweetheart conveying excellent verse, shayaris, love shayari, kinship shayari, musafir excursion to the brilliant statements darlings with the best of his endeavors and love for introducing genuine encounters by words.
love life poetry and Shayari in hindi
Hindi love shayari and poetry collection

shivam said...

We basically deals in graphic tees for men in india,best men's activewear online india and polo T-shirts for men in india.
graphic tees for men in india
best men's activewear online india

sonam said...

We are providing the best ispa sevices in singapore in woodlands with all safety measures. We have been awarded for the best business in 2019. You can also book your appointment online .We are providing our best in the industry.
cheap massage singapore
massage parlour singapore

shivani said...

Thanks for sharing such a useful article regarding blogging, you have cleared all my doubts

Clinical Psychologist & Psychotherapist in India
Qualified Mental Health Professional in India

shivani said...

We trained students for nda navy and airforce ,we are providing the best coaching in minimum fees. It is the best institute in all over in dehradun for nda airforce and navy coaching

Top airforce coaching center in Dehradun
Top Air force X group, Y group, Coaching center in Dehradun

Anonymous said...

We have 10 years in addition to of inclusion and involvement with planning private sun oriented plans. We give a wide scope of administrations in this division going from deals to interconnection. Our group has an inside and out comprehension of AHJ and Utility necessities and know about different codes and compliances.

Solar PV Design Services
Residential Solar permit plans

Anonymous said...

We are providing corporate services in singapore like company registration singapore,business registration singapore and company set up singapore etc.We provide our expertise in taxation to individuals and a wide range of businesses.
How to start business singapore
company set up singapore

kajal verma said...

call girls in noida
russian call girls in noida

RAGINIKAUR said...

independent escorts in saharanpur
housewife escorts service in saharanpur

Judy Hobbs said...

Tube fittings
Manifold valves
Needle valve
Ball valve
Globe valve

richard bryan said...

java tips At SynergisticIT we offer the best 5 minute java test

Kevin Mizern said...

You can make your tutorial in video formate and use youtube to post it. I sometimes buy youtube subscribers from this website https://soclikes.com/ and you can get your first subscribers from there

yahoomailforgotpassword said...

Steps to follow for Yahoo email was hacked ? There are mainly two methods to recuperate your hacked Yahoo Mail account.Go to the account settings in your Yahoo email was hacked and check in case hackers edited some info. You can restore any settings if necessary.

Speechelo Review said...

great work done Speechelo Review

Roblox Robux said...

this can be solved in good way Roblox Robux

blogo said...

great and best site for spotify codes

blogo said...

get the amazing psn card

dell driver download said...

best offers on itunes code

dell driver download said...

can check is this legit? walmart code

Gift Cards 2021 said...

amazing thanks for the info Gift Cards 2021

Quickbooks Expert said...

Nice & Informative Blog !
QuickBooks is an accounting software that gives you a broad platform to manage your accounting tasks. In case you find any technical issue in this software, dial QuickBooks Customer Service Phone Number 1-(855) 550-7546 for quick help.

DigitalMarketer said...

Hey! Good blog. I was facing an error in my QuickBooks software, so I called QuickBooks Error Code 15215 (855)756-1077. I was tended to by an experienced and friendly technician who helped me to get rid of that annoying issue in the least possible time.

mạnh thường said...

Thank you for sharing, I am very happy to know your blog. Here are some blogs I would like to send you. Please don't delete the link, hi
mo cong giao da
game nổ hũ đổi thưởng online
game bắn cá đổi thưởng 2021
làm cavet xe giá rẻ

BTree Systems, Software (IT) Training Institute said...

https://www.btreesystems.com
aws training in chennai
Python training in Chennai
data science training in chennai
hadoop training in chennai
machine learning training chennai

blogo said...

here we go for great amazon codes

Rajesh Adhikesavan said...

Male infertility specialist in chennai
Andrologist in chennai
Male fertility clinic in chennai
Andrology doctor in chennai
Infertility specialist in chennai

blogo said...

got to see this pokemon go coins

Gift Card said...

Good post Check this out netflix card

techno said...

must see this walmart gift card

Gift Card said...

Good One..netflix codes

techno said...

loved this among us

$ said...

Thank you for sharing this amazing piece of content. You are doing a great job, thanks for it.
Sad Shayari with Hindi Images, Sad Shayari for Facebook Whatsapp in Hindi Sad Shayari in Hindi Best Sad Shayari Collection in Hindi

William said...

Hi! Nice blog. We are also offering you QuickBooks Customer Service Number. If you need any help regarding QuickBooks issues, dial +1-855-756-1077 for instant help.

Unknown said...

Bài viết bạn rất hay:

Tác dụng của đông trùng hạ thảo

Tác dụng của đông trùng hạ thảo với nam giới

Tác dụng của đông trùng hạ thảo với phụ nữ

AnnaSereno said...

Hey! Excellent work. Being a QuickBooks user, if you are struggling with any issue, then dial QuickBooks Error 1328 (855)756-1077. Our team at QuickBooks will provide you with the best technical solutions for QuickBooks problems.

Theresa Hemmings said...

free google play codes
free google play codes
free google play codes
free google play codes
free google play codes
free google play codes
free google play codes
free google play codes

Unknown said...

Bài viết bạn rất hay:

Tác dụng của đông trùng hạ thảo

Tác dụng của đông trùng hạ thảo với nam giới

Tác dụng của đông trùng hạ thảo với phụ nữ

Authenticate said...

Erectile dysfunction treatment in chennai
Premature ejaculation treatment in chennai
Small penis size treatment in chennai
Ivf clinic in chennai

QuickBooks Error said...

Nice Post !
Our team at Quickbooks Error Support Number very well understands your frustration and thus, provides you immediate technical solutions to exterminate QuickBooks problems during this on-going pandemic.

fox king said...


What an amazing post admin really great post thanks for this.
viva video for laptop
viva video lite for PC
snapseed for Mac PC
how to change background color using snapseed
play stored for PC download
cam scanner apk for PC

Anonymous said...

http://selhozkorma.ru/index.php?option=com_comprofiler&task=userProfile&user=31717

QuickBooks Support Number said...

Nice Blog !
QuickBooks is one such accounting application that is developed to serve all the accounting needs of the business. In case you need immediate help with QuickBooks errors, call us on QuickBooks Customer Service Number to get the best technical assistance for QuickBooks.

All Government Job said...

This Blog Contain Good information about that. bsc 3rd year time table Thanks for sharing this blog.

Rachel Ross said...

Hey! What a wonderful blog. I loved your blog. QuickBooks is the best accounting software, however, it has lots of bugs like QuickBooks Error. To fix such issues, you can contact experts via QuickBooks Customer Service Phone Number

UsefulBloggy said...

This is great, thanks for your unique input on this topic because it really helped me wiwth what I needed help with. You can get 1 of many free PSN codes on the house.

Escort Service in Noida said...

can anyone tell me how to buy space in iphone Xr
Escort Service in noida

$ said...

Birthday wishes shayari

quatang24k said...


Bài viết rất hay!

Quà tặng doanh nghiệp giá rẻ tại hà nội

Quà tặng nhân viên cuối năm ở đâu chất lương và uy tín

QuickBooks Support Phone Number said...

Nice Article with full of information,Thanks for sharing with us.if you face any kind of trouble using QuickBooks, contact immediately:QuickBooks Customer Support number and get resolved your issue.

QuickBooks xpert said...

Nice & Informative Blog !
Our team at QuickBooks Phone Number never charges extra fees from our customers especially har to the ongoing crisis.

quatang24k said...


Bài viết rất hay!

Quà tặng doanh nghiệp giá rẻ tại hà nội

Quà tặng nhân viên cuối năm ở đâu chất lương và uy tín

Marry joe said...


Thanks for sharing this useful information with us. Keep updating us with your awesome ideas.In case if you need QuickBooks technical assistance, you may contact at:QuickBooks Support phone numberfor quick solution.

$ said...

Attitude Status
Attitude Status
Attitude Status
Attitude Status
Attitude Status

Foundation Graphhene said...

It is an education NGO which works for education for unprivileged children who are under difficult circumstances, such as child labour, children of poorest of the parents, children with rare disabilities and slum children.
Visit Our Website To Donate and Help The children for there welfare:- Ngo For Poor Children in Noida

Shivam Kumar said...

You have explained the things very well. Thanks for sharing a nice Example.Visit Nice Java Tutorials

$ said...

Love Shayari Hindi
Love Shayari Hindi
Love Shayari Hindi
Love Shayari Hindi
Love Shayari Hindi

$ said...

Love Shayari in Hindi
Love Shayari Hindi
Love Shayari in Hindi
Love Shayari in Hindi
Love Shayari in Hindi

$ said...

Love Shayari
Love Shayari
Love Shayari
Love Shayari
Love Shayari

$ said...

Shayari
Shayari
Shayari
Shayari
Shayari

$ said...

Hindi Shayari
Hindi Shayari
Hindi Shayari
Hindi Shayari
Hindi Shayari

$ said...

Love Hindi Shayari
Love Hindi Shayari
Love Hindi Shayari
Love Hindi Shayari
Love Hindi Shayari

quatang24k said...


Bài viết rất hay!

Quà tặng doanh nghiệp giá rẻ tại hà nội

Quà tặng nhân viên cuối năm ở đâu chất lương và uy tín

MilesWeb said...

Are you looking for good hosting? So read Full Comparison of MilesWeb vs GoDaddy.

Nino Nurmadi , S.Kom said...

Nino Nurmadi, S.Kom
Nino Nurmadi, S.Kom
Nino Nurmadi, S.Kom
Nino Nurmadi, S.Kom
Nino Nurmadi, S.Kom
Nino Nurmadi, S.Kom
Nino Nurmadi, S.Kom
Nino Nurmadi, S.Kom
Nino Nurmadi, S.Kom
Nino Nurmadi, S.Kom

$ said...

Attitude Shayari
Attitude Status in Hindi

Authenticate said...

real time web app development in usa
web design company in usa
corporate web design in usa
html5 ui ux design in usa
brochure design in usa

Admin said...

Thanks for the interesting content. I like your post and your blog is amazing.

If you are interested in Video Downloader apps you can check my blog site. It is new and really informative.

VidMate Movie Download

Rohit said...

It was reaaly wonderful reading your article. # BOOST Your GOOGLE RANKING.It’s Your Time To Be On #1st Page
Our Motive is not just to create links but to get them indexed as will
Increase Domain Authority (DA).We’re on a mission to increase DA PA of your domain
High Quality Backlink Building Service
1000 Backlink at cheapest
50 High Quality Backlinks for just 50 INR
2000 Backlink at cheapest
5000 Backlink at cheapest

Himanshu said...

IPL Astrology 2021

IPL Bhavishyavani Prediction

IPL Prediction Astrology 2021

Today IPL Toss Bhavishyavani 2021

IPL Toss Prediction 2021

IPL Match Prediction Astrology 2021

Today IPL Match Prediction 2021

IPL Bhavishyavani Astrology 2021

ipldevta said...

Hello Dear, Your writing skills are so cool. I love your content that you provide us about IPL Bhavishyavani Prediction 2021.
IPL Prediction 2021
IPL Bhavishyavani 2021
IPL Today Toss Prediction 2021
IPL All Match Toss Bhavishyavani Prediction Astrology 2021
Who Will Win Today IPL Match 2021
IPL All Match Prediction Astrology 2021
Today IPL Match Bhavishyavani Reports 2021
आज के आईपीएल मैच का भविष्यवाणी 2021
Today IPL Match Prediction Astrology 2021

Persephone Summers said...

Buy weed online without medical card
Buy marijuana flowers online
bc big bud strain leafly
buy vape & catridges
buy psychedelics online
Buy white ice moon rock
Buy marijuana edibles online
Buy marathon og online

lkrasilnikovaludmila1976 said...

Unders has spent over 20 years as a student of health and wellnesserin moriarty nude kajal aggarwal boobs sonam kapoor nude blake lively nude gwyneth paltrow nude tamana sex disha patani xnxx elizabeth olsen boobs hansika motwani boobs shilpa shetty porn

$ said...

Excellent StuFF Thanks for sharing,...
Attitude Shayari in Hindi

Victor said...

TKO Carts

Unknown said...

To contact us, we have created a contact section for your website. Call girls in Bangalore You can contact us, we are also connected with social media. :) Call Girls In Bangalore

Medical Cannabis said...


Steroids for sale online




Buy Steroids Online



Anabolic Steroids for Sale



anabolic steroids injectables



Best Oral Anabolic Steroids




Best Human Growth Hormone For Sale



Sex Pills For Sale



Bulking Steroids Cycle for sale



clenbuterol 100 tabs

Frontier Log Homes said...

Amazing article written, Get to now about:- tutoring in orange county

«Oldest ‹Older   1 – 200 of 217   Newer› Newest»

Post a Comment