In the previous sections, we have discussed the basic persistence elements of Java se jpa. In this article, we will look at a sample application and discuss in detail how to apply JPA in your development.
First, let's take a look at the requirements of the sample application, which can be downloaded here. This is an application about license management. In this example, there are many applications, each of which has multiple versions, and each version has one or more related licenses. There is also a group of users who may be associated with any of the licenses. We want to create an application that can manage all these elements.
Now let's start with the entity. They are all in their own program packages, and are not mixed with the application code. It is worthwhile to do so. In large projects, you can use entities as separate projects, so that they can be reused in other projects more easily. We have created four entities: application, version, licence, and user. So let's take a look at the role of each object.
In the Application class, we have a one-to-many relationship with the version class. Below is a part of the application method; we skip the ID and name attributes, because they are similar to the content we discussed earlier.
@ Entity
Public class application {
...
Private list <version> versions = new arraylist <version> ();
...
@ Onetoetype (mappedby = "application", cascade = cascadetype. All)
Public list <version> getversions (){
Return versions;
}
...
}
Last month, we talked about the mappedby parameter. The new thing in this article is the cascade parameter. This cascade parameter is used to control operations performed by the persistence engine, thus affecting the capabilities of other tables in the database. By default, there is no Cascade, so you need to manage the content of the set explicitly for changes to the set. Check other cascadetype values and find the implicit operations: All, persist, merge, remove, and refresh. For example, set cascadetype. persist will only cascade objects. Therefore, if a new version instance is added to the version list, the application instance will be updated to cascade operations, to save the new version in the underlying data. Cascadetype. Merge will apply the same rules for updates, while cascadetype. Remove will also delete the content in the set. Cascadetype. Refresh is used to re-read the instance from the database for Cascade operations. We will discuss it later.
Now let's look at the version class. We already have a many-to-one (@ manytoone) Relationship with the application and another cascade set. Now let's take a look at the license.
@ Entity
Public class version {
...
Private application;
Private set <licence> licences = new hashset <licence> ();
...
@ Manytoone
Public Application getapplication (){
Return application;
}
...
@ Onetoetype (mappedby = "version", cascade = cascadetype. All)
Public set <licence> getlicences (){
Return licences;
}
...
}
Following the idea of the program, we can see the licence class. Stack ends here.
@ Entity
Public Class Licence {
...
Private version;
Private set <user> Users = new hashset <user> ();
...
We also have a set to represent a group of users. The ing to version is handled by @ manytoone annotation.
@ Manytoone
Public version getversion (){
Return version;
}
Now we have come to the most important ing part in this example. Many licenses can refer to many users and all of us use @ manytomany (many-to-many) annotations to express this situation.
@ Manytoyun
@ Jointable (name = "licenceusers ",
Joincolumns =,
Inversejoincolumns =)
Public set <user> getusers (){
Return users;
}
}
@ Jointable annotation allows us to control @ manytomany and replace its default value. The name parameter is the name of the table to be created to save many mappings. The joincolumns parameter allows us to set join. You may want to know why we add parentheses to its independent variable. This type of independent variable is an array, so we still need to add parentheses even if there is only one value. The value is a @ joincolumn annotation, which is used to set the name of the data column in the database. The same syntax can be used for the inversejoincolumns parameter, although it is a multi-to-many relationship.
Finally, let's take a look at the user class. In this example, we assume that the user is identified by a unique user name. Therefore, we can use it as an identifier instead of @ generatedid to generate a unique ID value that we can use in other entity classes.
@ Entity
Public class user {
Private string username;
Private list <licence> licences = new arraylist <licence> ();
Public user (){}
@ ID
Public String GetUserName (){
Return username;
}
...
Now let's take a look at how to create a ing to the license. This is a multi-to-many (manyto) relationship, of course, that is, @ manytomany.
@ Manytomany (mappedby = "users ")
Public list <licence> getlicences (){
Return licences;
}
...
}
Here we use the mappedby parameter to point to the user attribute of the licence class. This allows the persistence engine to use the @ jointable specified in the licence.
This is the entity. Now we turn to the topic of how to manipulate them. The example we wrote is based on the Java Standard Edition platform, so that we can create a manager class as in the previous article and instantiate the EJB/JPA layer from this class. The following is a separate licmanstore. Java class to be created.
Public class licmanstore {
Private entitymanagerfactory EMF;
Private entitymanager em;
Private Static licmanstore myinstance;
Public static licmanstore getstore (){
If (myinstance = NULL ){
Myinstance = new licmanstore ();
}
Return myinstance;
}
Private licmanstore (){
EMF = persistence. createentitymanagerfactory ("appman ",
New properties ());
Em = emf. createentitymanager (persistencecontexttype. Extended );
}
...
This constructor differs significantly from the previous example: it uses persistencecontexttype. extended. By default, entitymanagers uses the persistencecontexttype of transaction to create the object. This means that the entity can be managed only when active transactions are processed in the process. Once the transaction processing ends, the entity is separated from the Entity Management Program, so that we can discard it. Extended context type indicates that this detachment will not occur, even if the entity is still hosted after the transaction is processed. This means that you do not need to worry about whether the collection is suspended, because the Entity Management program can be used to complete the necessary retrieval operations. When we want to maintain and update/merge entities, or delete entities from the database, we still need to obtain entitytransaction. For example, we want to save a new application entity:
Public void saveapplication (application ){
Entitytransaction Tx = em. gettransaction ();
TX. Begin ();
Em. persist ();
TX. Commit ();
}
If you look at the licmanstore source code, you will find that the application only has the save, update, and delete operations, the user only has the SAVE and delete operations, and the licence and version have no way. They are not forgotten, but they are not required. Remember, we have set a stack for the relationship between application, version, and licence. This means that all we need to do is update the application, and all the operations required by version and licence have been completed. If you observe the controller. Java code, you will see the above process. First, let's create an application:
Void addapplication (string appname) throws updateexception {
If (licmanstore. getapplication (appname )! = NULL)
Throw new updateexception ("an application"
+ Appname + "already exists ");
Application APP = new application ();
App. setapplicationname (appname );
Licmanstore. saveapplication (APP );
Populateapplications ();
Ui. setselectedapplication (APP );
}
There is nothing special worth noting here; the populateapplications method is used to retrieve all applications, fill in the user interface model used to display the list, and then select the just-maintained application in this list. Now let's add a version to the application. Here, currentapplication is the instance of the selected application, which is named by the new version name string:
Void addversion (string versionname ){
If (currentapplication = NULL) return;
Version ver = new version ();
Ver. setversionname (versionname );
Ver. setapplication (currentapplication );
Currentapplication. getversions (). Add (VER );
Licmanstore. updateapplication (currentapplication );
Populateversions ();
Ui. setselectedversion (VER );
}
What we need to do is create a new version, set its name, set its parent class to currentapplication, and then add the version to the list of currentapplication versions. To perform these operations on the database, we need to update/merge currentapplication. However, this code has a problem. The version you just created is not selected in the list. If the Entity Manager has taken over the stack, the newly added version instance cannot be hosted. It is just a shell, which is similar to storing the application in addapplication () objects are clearly different from managed objects. The code here does not work, because when we try to select a version in the list at the end of the method, the list does not contain the version shell we used at the beginning, there is only one other managed version instance based on it. The solution is simple. You only need to retrieve the Hosted version. The existing getversion method processes the retrieval operation:
Version getversion (Application app, string name ){
Try {
Query q = em. createquery ("select ver from version as ver
Where application =: app and versionname =: Name ");
Q. setparameter ("app", APP );
Q. setparameter ("name", name );
Return (Version) Q. getsingleresult ();
} Catch (noresultexception NRE ){
Return NULL ;}
}
The getsingleresult method of a query returns only one result. If no result is found or multiple results exist, an exception is thrown. Here, we interpret the returned result as "null ". Now we can change the addversion method of controller to use it. In addition, we can also use it to check that we have not created duplicate names, an exception occurs when the name is already created:
Void addversion (string versionname) throws updateexception {
If (currentapplication = NULL) return;
If (licmanstore. getversion (currentapplication,
Versionname )! = NULL)
Throw new updateexception ("version"
+ Versionname + "already exists ");
...
Populateversions ();
Ver = licmanstore. getversion (currentapplication, versionname );
Ui. setselectedversion (VER );
}
Now, the created version is automatically selected. As an exercise for readers, we leave a similar error in the code that creates the licence for fixing.
One thing to remember when using the extended persistence context is that you do need to keep the object synchronized. There are two ways to do this. For example, when we assign a user to a licence, we must remember to update the license set of the user in the memory and add the user to the licence user set. In the adduserstolicence method of controller, we ensure that the user and licence lists are added:
Void adduserstolicence (list <user> U) throws updateexception {
If (currentlicence = NULL) Throw
New updateexception ("no licence selected ");
Currentlicence. getusers (). addall (U );
For (User ut: U) ut. getlicences (). Add (currentlicence );
Licmanstore. updateapplication (currentapplication );
Populatelicenceusers ();
}
Of course, this may be unrealistic. Another way is to let the Entity Management Program refresh the managed object and synchronize it with the database. To do this, we need to add a refresh method to licmanstore:
Void refresh (Object O) {em. Refresh (o );}
Only refresh the user instance from the database:
Void adduserstolicence (list <user> U) throws updateexception {
If (currentlicence = NULL) Throw
New updateexception ("no licence selected ");
Currentlicence. getusers (). addall (U );
Licmanstore. updateapplication (currentapplication );
For (User ut: U) licmanstore. Refresh (UT );
Populatelicenceusers ();
}
If we have to use the default transaction processing context for the Entity Manager, the Entity Manager is often newly created and there will be no residual managed objects that have not been refreshed from the database.
Finally, let's take a look at the "License Report" for listing all the licenses owned by a user. It will be displayed when you double-click a user in the user list. This is obtained by sorting out (the relationship between elements) and using the name and version of the application obtained from the user class list of licences.
...
Stringbuilder sb = new stringbuilder ();
...
For (licence l: User. getlicences ()){
SB. append (L. getversion (). getapplication (). getapplicationname ());
SB. append ("");
SB. append (L. getversion (). getversionname ());
SB. append ("");
SB. append (L. getlicencekey ());
SB. append ("");
}...
There is no direct access to the database, because the Entity Manager is responsible for calling them when the licence, version, and application classes are accessed, thanks to the extended persistence context. This is of course a price, especially when the application buffer the accessed object, the memory usage, this is why you should always consider using the transaction processing context and creating entity management programs as needed when planning how to use JPA.
This article focuses on using JPA in Java Standard Edition. JPA is not only used for Java SE, but is rooted in Java Enterprise Edition. The classes we use here can also be used in enterprise applications without any changes. The real change lies in how you get the Entity Management Program and where to get its configuration. The portability of the architecture is the real highlight of JPA. The technology learned in Java SE can also be transplanted to Java ee.