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.
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
Other set-up details are not necessary to understand. If you are interested in them anyway, we described them in an independent post.
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:
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
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 inMETA-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 entitiesPerson
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 theclear
method on the entity manager to detach all entities without closing it.If you wish to detach only one entity, use the method
detach
: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:
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.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:
Note: entity change does not have to happen in between transaction begin and commit. Following works too:
Changes on detached entities are ignored. The next test detaches an entity and commits a transaction. Changes are not saved into the database:
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:
If an entity with the same id already exists, either persist or a commit method throws 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:
Delete
Use theremove
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:
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:
If the other thread deleted the entity, the
refresh
method throws an EntityNotFoundException
exception:The
refresh
on detached entity throws an exception: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:
Detached entity is not able to lazy load data:
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:'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: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.
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.
If you keep the model consistent, JPA will generally work as intuitively expected. Few less intuitive features are described in following sub-chapters.
Saving the owner is enough to save the relationship:
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.Saving the owner is enough to save the relationship:
Saving only the inverse does not save the relationship:
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.
Hibernate does that automatically, but if you use OpenJPA you have to do a little configuration.
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 usemerge
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: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 thecascade
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: theREMOVE
can be used only with OneToOne
and OneToMany
relationships.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: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.The cascading of
refresh
, merge
and detach
passes only through entities that are already loaded.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.
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.
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.
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.
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.
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 namedPROPERTYNAME_ID
. This column should contain references to the inverse side entity.The previous relationship corresponds to following db structure:
Optional
If the annotation attributeoptional
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.
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. theoptional
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:
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:
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.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:
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.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.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:
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 aCollection
, 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:Default Database Structure
Unless configured otherwise, JPA assumes that the owners table, e.g. the one with entities onManyToOne
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:
Db structure for a version with a map:
Loading
The owner, e.g.@ManyToOne
side of the relationship, is eagerly loading by default. Unlike in one-to-one relationship, it is possible to configure it to be lazy:
@ManyToOne(fetch=FetchType.LAZY) private LazyInverse inverse;
Cascading
All cascading types are compatible with one-to-many side of the relationship. The opposite many-to-one side does not support theREMOVE
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.Additionally, if you used a map, be careful about changing the value of the map key property.
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:
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:
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:
Note: the
Warning: the annotation
It might be tempting to use either
Unidirectional relationship:
If a
If a
The property specified in
@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 aCollection
, 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:Default Database Structure
Unless configured otherwise, JPA assumes that the relationship is kept in a table namedOwnerTableName_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 haveUNIQUE
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:
Db structure for a version with a map:
Note: latest OpenJPA implementation (2.1.1) has a bug:
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.
If the
Set orphan removal:
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 asUNIQUE
. 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:
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. 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: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:Default Database Structure
Unless configured otherwise, JPA assumes that the relationship is kept in a table namedOwnerTableName_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:
Note: latest OpenJPA implementation (2.1.1) has a bug:
Cascading
TheREMOVE
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.Additionally, if you used a map, be careful about changing the value of the map key property.
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.
138 comments:
Nice and quite Comprehensive post. Thanks for putting effort and sharing information.
Javin
Quick tip on LDAP Authentication using Spring Security
Hi Javin,
thank you. I'm glad to hear that someone liked it.
Meri
Your post is very good. Previously I have been working with XML, but now it's time to move to Database.
Hi Maria,
Nice blog! Is there an email address I can contact you in private?
very very nice post and thank you very much for your easy explanations...
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
This is great stuff, thanks so much!
Are the Relationship examples all correct?
@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.
can u enhance this article to include 'validation'?
validation of entity classes with the underlying database itself.
(PS: something like Hibernate Validator)
@Sriram Viswanathan Thank you.
@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.
Excellent tutorial for clear understanding on JPAs
!
I liked your discussion of Consistency most....
Perfect job, thank you.
very nice post for beginners ,,, thank you so much for putting some effort ....
Nice work, congrats :-)
V nice post, to the point and as short as possible. Thanks.
Very comprehensive info on relations, props for explaining the gotchas :D
Thank you, Maria :)
Very good tutorial - thank you very much
Thanks for documenting this. It was very useful.
Thank you very much, very good tutorial
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
Helpful
i found this article more informative, thanks for sharing this article!
showbox
showbox for pc
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
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
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
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
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/
poeple are really enjoying your content can you please post a content upon Mobile Phone Prices in Pakistan
hi ,
thanks for the share.It cleared the air about JPA.Know i can say i am aware about the
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
It was great to read your article. Read mine here
Mirakee
Framasphere
Mundoalbiceleste
Thanks for the article. Good one.
Nice article for android articles
nice post...
We offer 100% active and natural instagram followers from targeted location and worldwide region : Buy Active Instagram Followers India
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
This is a great explanation, thank you so much!
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à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/
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
IPL Toss Astrology
Today Match IPL Bhavishyavani
“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 !”
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
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
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 sharing such a useful article regarding blogging, you have cleared all my doubts
Clinical Psychologist & Psychotherapist in India
Qualified Mental Health Professional in India
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
java tips At SynergisticIT we offer the best 5 minute java test
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
great work done Speechelo Review
this can be solved in good way Roblox Robux
great and best site for spotify codes
get the amazing psn card
can check is this legit? walmart code
amazing thanks for the info Gift Cards 2021
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.
here we go for great amazon codes
got to see this pokemon go coins
must see this walmart gift card
loved this among us
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.
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.
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
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.
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.
Attitude Status
Attitude Status
Attitude Status
Attitude Status
Attitude Status
You have explained the things very well. Thanks for sharing a nice Example.Visit Nice Java Tutorials
Love Shayari Hindi
Love Shayari Hindi
Love Shayari Hindi
Love Shayari Hindi
Love Shayari Hindi
Attitude Shayari
Attitude Status in Hindi
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
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
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
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
Excellent StuFF Thanks for sharing,...
Attitude Shayari in Hindi
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
buy mr nice online
buy white ice moon rock
buy juicy fruit online
buy marathon og Strain online
exotic carts packaging
facewreck
legal psychedelics for sale
buying acdc strain online
backwoods wild n mild
buy liberty caps
Sorry Status
badmashi status
Download New Mp3 Ringtone Play Mp3 Ringtone And Listen Mp3 Ringtones
Ringtones Mp3 Download
Ringtones Mp3 Download
Ringtones Mp3 Download
Ringtones Mp3 Download
Data Recovery Professional
Headout discount, Headout Singapore, NYC, Paris, oyo hotels near delhi railway station, best top hotels in maldives, web hosting affiliate program in india Headout deals, tour and travel write for us,York, cheap hotels in new york city seo agency in mumbai, gatlinburg indoor pool, gatlinburg hotels with indoor pool, free, profile creation sites , top 500 honeymoon hotels in maldives, web hosting oyo rooms in goa near baga beach, Hotels web hosting Hotels Near Niagara Falls Canada, Hotels
New Telugu Mp3 Telugu Ringtones Download New Mp3 Ringtone Download
Best Mod App Premium Ludo King Mod Apk Board Game Mod App
India No.1 Ludo King Board Game Mod Application Download Link Ludo King Mod Apk
Download New Updated Mod Apk for Ludo King
TKO Carts
TKO Extracts
TKO Edibles
Thanks For Awesome Content Check this Out:-
Epson Printer Error 0X9a Occurs when paper jam, bent or damaged rails, or dislocated encoder strip in the printer. Our team provides online solutions 24/7 for Epson Printer Customers. Follow the link below.
Nice Blog !
Get quick and effective assistance for QuickBooks POS Error 100060.We provide quick and effective service to every QuickBooks client.
mp3 Gujarati ringtone download for android and iphone.
Thanks for sharing such great content. Where else can I get this kind of information in such an ideal way.
Escort service in aerocity
Escort service near aerocity
Aerocity escort service
Escort service in delhi
Delhi escort service
Escort service in goa
Your post is very nice and very different. Your post will be very helpful for me as I love these articles.
email helpline number
printer helpline number
antivirus support number
tech expert phone number
Wow great post! Thanks for informative blog, it's very helpful.
Escort service in aerocity
Escort service near aerocity
Aerocity escort service
Escort service in delhi
Delhi escort service
Escort service in goa
Ludo King is Indian Board Game That Played By Anyone Who Want, Here Some Vip features of Ludo King App That User Want to Use Then Here Download Link of Ludo King Modified Application
hi,
ok anh ơi
bull pháp hà nội
Địa chỉ bán chó Corgi tốt nhất năm 2021
best weed market
buy medical weed online
buy granddaddy purple weed online
buy white widows strain online
buy gelato strain exotic online
buy og kush strain online
moon rocks for sale online
buy silver haze strain online
xanax-0-25-mg/
Thanks for this excellent but sometimes you just never know what people will respond to.
source
I have learned a lot from your article and I’m looking forward to applying
Cheap LMS
Affordable LMS
Best LMS For School
This was a fantastic blog. A lot of very good information was given,
Top HR Consulting Firms in Bangalore
Best HR Recruitment Agencies in India
Wow, I really find your article helpful. you might also want to check my content that is related to your article/blog.
Superb post. You can also check Latest Diwali Wishes in hindi and Diwali Wishes in hindi font
Good morning Quotes in hindi
This was a fantastic blog. A lot of very good information given,
Top HR Consulting Firms In Delhi NCR
Roles Of HR In A Company
If you're planning to hold an Court Marriage in delhi court wedding, you should have the right understanding of the process for it . Court marriage Dehradun
very nice blog thank you
Thiruvilaiyadal book
Vrata Puja Book
thanks for providing such a great article,this article is very help full for me, a lot of thanks sir
HR consultants india
HR service
Very informative and well written post! Quite interesting and nice topic chosen for the post. colourist logo
Really nice blog. Please visit www.unogeeks.com
https://unogeeks.com/mulesoft-training/
Thank you for every other informative web site. Where else may I am getting that kind of information written in such a perfect way? I’ve a undertaking that I’m just now working on, and I have been on the look out for such information.
BA 1st Year Exam Result | BA 2nd Year Exam Result | BA 3rd Year Exam Result.
Home Interior Designer in Faridabad
Interior Design Service in Faridabad
Architecture Service in Faridabad
3D Interior Designing Services in Faridabad
home decor items in faridabad
residential architects in faridabad
civil interior designer in faridabad
The Distance Learning Centre in Delhi is a place where people can pursue their studies through distance education. This type of educational institute is located in Satya Niketan. It offers distance education courses in various fields, such as IT management and business. Education is an essential part of human development, as it determines your life chances and career options. Education provides you with a solid foundation for advancement and can lead to a rewarding career.
Dayitwa Education is committed to providing the necessary Online learning Education to reach the highest platform.
This institute offers a variety of courses for both undergraduate and postgraduate students. This distance learning center in Satya Niketan is accredited by Many Universities and regulated by the same rules as the other constituent colleges. Correspondence education is an excellent option for those who are unable to attend regular classes. During this type of study, you receive course materials by post and return them to the authority for evaluation. There are a number of distinguished institutes in Delhi that provide correspondence education.
Correspondence colleges in Delhi offer a convenient option for completing your education without interrupting your regular job.
Online Strikers is the Best digital marketing agencyin India. This is best digital marketing agency has been giving useful promoting administrations to organizations of different kinds for the last numerous years. other digital marketing in Delhi that self-announce themselves to be the Best digital marketing agency in Delhi. We are the Best digital marketing agency in Delhi in light of the effective work we had with our clients and the criticism we have gotten from them. Online Strikers is maybe the best digital marketing agency in Delhi that doesn't settle just on its web or web based advertising administrations. We the group at Online Strikers go each mile and past to assist our clients with winning customary clients through viable promoting methodologies and to get that done, we likewise offer disconnected showcasing administrations. Because of our conviction that clients are all over, at each stage and at each stage, we don't believe our clients should lose even any single one of them, we offer all types of digital marketing services like seo services, web development, smo services cpm , etc. to our clients more customers. The way we deal with each client's record proficiently is one reason. We offer a complete package of all the things mentioned above at only the cost of the service, no matter what service you choose to take.
Iam very pleased to read your article
python training in hyderabad
Thanks for sharing wonderful information nissan magnite sales
I thoroughly enjoyed reading this article, and I must say that it is exceptionally well-written and informative.
good contant
earn digital marketing skills to implement them on your website and social media to generate traffic and get maximum ROI. We teach 50+ modules in our Masters in Digital Marketing Course along with 02 Months onboard mentorship training session under our expets digital marketers for Students, Working Professionals, and Entrepreneurs.
Delhi Institute of Digital Marketing: Empowering Digital Success
Experience comprehensive digital marketing training at the Delhi Institute of Digital Marketing (DIDM). Our courses cover SEO, social media, PPC, and more, providing hands-on learning with industry experts. Gain practical skills, access cutting-edge tools, and connect with industry professionals. Choose flexibility with online, classroom, or blended learning options. Unlock your digital potential with DIDM today.
Shop the latest earrings online at 5elements - exquisite designs, high-quality craftsmanship, and a stunning collection to elevate your style.
visit us - Buy latest earrings online
Make your business stand out with Scribit Solutions, a Noida-based digital marketing agency that can help you improve your website ranking, create effective targeting ads, create compelling email campaigns, and build powerful social media followings. We understand the importance of branding and how to leverage online presence to attract valuable customers. Our team has years of successful experience in designing and implementing measurable marketing initiatives that have helped businesses from all industries grow exponentially. Contact us today to discover what Scribit Solutions can do for you!
Wow Nice.. Really
Nice Blog Keep Posting.
Testing Tools Training in JNTU
Hey nice information,thank you for this,keep posting.
PYTHON FULL STACK TRAINING IN HYDERABAD
thanks for providing such a great article, this article is very help full for me, a lot of thanks sir
Java Training in KPHB
Surprise Your Loved Ones With A Premium Gift Hamper. Buy This Luxury Indulgence Premium Gift Hamper Online At Best Price In India From OasisBaklawa
nice blog ,keep posting
visit now!
devops training Institute In Hyderabad
nice blog keep posting thamku sir
power bi course in kukatpally
nice blog keep posting
power bi training near me
power bi institutes in hyderabad
Learn The Benefits of Hiring a Digital Marketing Company in Delhi NCR can transform your business. Gain expertise, improve strategies, and maximize your online impact.
Discover Top Strategies Used by Digital Marketing Companies in Delhi NCR. Enhance your business with proven techniques for online success.
Choosing the best digital marketing company in Noida for your brand. Explore our comprehensive guide to enhance your online strategy and maximize results.
Enhance your online visibility with our comprehensive Digital Marketing Services in India. Tailored solutions to boost engagement and grow your business.
Nice blog keep posting
power bi training institute in hyderabad
power bi training in hyderabad
power bi institutes in hyderabad
power bi institutes in hyderabad
power bi institutes in hyderabad
power bi institutes in hyderabad
Looking for expert Web Development Company in India? Our skilled team creates responsive and user-friendly websites to help your business thrive in the digital landscape.
Post a Comment