As much as possible, we should use an intentional approach to internal conversion of Kuali Rice modules from OJB to JPA.
This document outlines a general approach to how we will deal with each module and the steps we will take.
- If possible, do a module a chunk at a time. In some cases we have clusters of tables that are related and will need to be converted together, but we will often have sets of tables (even within a given module) which are independent from each other and can be converted independently to allow for some amount of testing in between.
- For example, the Rule-related tables in KEW could be converted separately from the Document-related tables since there are no inter-dependent relationships between those tables at the ORM layer.
- The first step will be to configure JPA and the persistence and metadata providers for whichever module you are working in. Examples of this can be found in the JPA Design Docs or in some of the existing modules which have already been converted.
- Next step will be to do the JPA mapping work.
- Annotations should be used as the preferred method for annotating
- Remember to use @PortableSequenceGenerator for sequences
- Objects which extend PersistableBusinessObjectBase should continue to do so for reasons of compatibility
- Care should be taken to not change the database structure at all, we should Map our objects directly onto the existing database structures. Version compatibility requirements will require us to keep these table structures the same in many cases anyway.
- Reimplement any service implementation which uses a DAO to attempt to use DataObjectService instead. If DataObjectService cannot be used, then we will use a DAO.
- Reimplement any remaining DAO methods using JPA.
- As part of this conversion, you should be able to get rid of the OJB version of the DAO's and any methods on the DAO that are no longer needed. Feel free to make these changes as you go.
- The JPA daos should be named MyDaoJpa. Don't use MyDaoJpaImpl because that's just redudant
- Inject the SharedEntityManager into the DAO. Ideally we will use @PersistenceContext(unitName="blah") for this.
- DAO methods should use JPQL and Named queries for any query which can be statically defined. Named queries should be defined using the @NamedQuery annotation and placed on the mapped entity. They should be named like "EntityName.daoMethodName" wherever possible.
- In cases where a query needs to be built up dynamically, use the JPA 2.0 Criteria API instead.
- Never build JPQL queries using String concatenation
- As you are working through the conversion, write integration tests which test the actual persistence and querying of these JPA-mapped entities. In some cases there will be pre-existing tests to run and/or fix. In most cases you will need to write new integration tests!
- Ensure that any other integration tests for the module are passing. It may be good to wait until you get to the end of the module conversion and then run all integration tests and assess the situation from there.
- Remove OJB repository files when done and OJB daos and/or spring beans UNLESSthe module has legacy KNS screens which depend on the OJB functionality and mapping being in place.
- Note that in most cases it will not be possible to remove the OJB mapping file because most modules have at least some set of KNS-based screens. KRMS, KEN, KSB, and eDocLite are probably the exceptions.
- Be sure to document here any issues, gotchas, or best practices you encounter as part of your work on the JPA mappings.
Issues, Gotchas, Best Practices, etc.
- Ran into an issue where if you have a relationship like the following:
- Then if
B_IDin your database is NOT NULL, you will get a constraint violation if you try to save an instance of "MyA" even if "z" is non-null, it will try to insert NULL into that column. I think the reason is because it's doing an insert and then later an update. If you change the join column to be nullable false as follows it works:
- One last thing, my specific example above may not actually trigger this case. In real life when I ran into this it was with RouteNodeInstance and RouteNode in KEW which have some someone complex and circular relationships. Just the above is an easier example to show related to the use of nullable. I think the bottom line is that anywhere you have a NOT NULL column, you should use nulable=false.
- The last couple of comments on these thread seem to confirm my suspicion that this may be related to circular relationships: http://www.eclipse.org/forums/index.php/t/298970/
- Understand how flushing works in JPA
- It is very important to read about and understand how the concept of "flushing" works in JPA. See here: http://en.wikibooks.org/wiki/Java_Persistence/Persisting#Flush
- This is different from the behavior of OJB which essentially flushed after every "store" invocation. In certain situations, you may want to consider flushing manually if you are having issues with your converted code. One specific case where you might have problems is if you are depending on generated values such as version numbers or ids. Depending on the situation, these may not get generated or updated until after a flush has occurred.
- It does appear, however, that if you are using the custom @PortableSequenceGenerator annotation that these id's get generated as soon as DataObjectService.save is called.
- By default, the JpaPersistenceProvider does not "auto flush" for you after you issue a DataObjectService.save
You can force it to flush by passing a persistence option, as follows:
- Note that the above does a flush on the entity manager which will flush all changes to the database. There is no way in JPA to flush just the changes for your specific entity that you are persisting or merging.
Alternatively, you can also do a global flush manually. The code below is equivalent to the previous code block:
- In the case above, the Class object that is passed to flush is simply used by the data infrastructure to identify the appropriate JPA persistence context in which to perform the flush.
- When creating a NamedQuery be sure to use an alias for the table
If you receive an error similar to below you may be missing an alias. Example: select r from KUL_RICE_T r