I use EJB3 container managed persistence i.e an EntityManager is injected via #PersistenceContext annotation. The persistent context then may be propagated to nested EJBs. Transactions are also managed by the contaner (glassfish).
Usually I would drop persistence.xml into META-INF directory and the container would work out which provider to use and how to configure the EntityManagerFactory (based in hibernate specific properties).
My problem is that I need to hook into the EntityManagerFactory configuration process.
Particularly I need to change discriminator values in some PersistentClasses before the EntityManagerFactory gets configure'ed (frozen for any change).
This is how I do it with Spring, but need to do similar with pure EJB3 CMP (or may be with the help of Spring).
public class AnnotationSessionFactoryBean extends org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean {
/** Log4j logging instance. */
protected static Logger log = Logger.getLogger(AnnotationSessionFactoryBean.class);
//some data preloaded from the database using jdbc
private Map<String, DatabaseConfiguration> configs;
#Override
protected void postProcessAnnotationConfiguration(AnnotationConfiguration config) throws HibernateException {
//Load and process dynamic Mappings.
Iterator classMappingsIter = config.getClassMappings();
while(classMappingsIter.hasNext()) {
PersistentClass persistentClass = (PersistentClass) classMappingsIter.next();
String discriminatorValue = persistentClass.getDiscriminatorValue();
if(discriminatorValue != null) {
log.debug("DiscriminatorValue before [" + discriminatorValue + "]");
//here I replace discriminator values.
//The Discriminator values are coded in the annotations
//as names (words). These words need to be replaced with ids
//previously loaded from the database using jdbc.
//The names are constant in all environments, however the ids are
//are different.
discriminatorValue = StringUtil.replacePlaceholders(discriminatorValue, configs);
persistentClass.setDiscriminatorValue(discriminatorValue);
log.debug("DiscriminatorValue after [" + discriminatorValue + "]");
}
}
super.postProcessAnnotationConfiguration(config);
}
/**
* #return the configs
*/
public Map<String, DatabaseConfiguration> getConfigs() {
return configs;
}
/**
* #param configs the configs to set
*/
public void setConfigs(Map<String, DatabaseConfiguration> configs) {
this.configs = configs;
}
}
Thanks in advance,
Anton
I think I have found the solution.
The class org.hibernate.ejb.HibernatePersistence can be overridden.
public class HibernatePersistenceCustom extends org.hibernate.ejb.HibernatePersistence {
/** Log4j logging instance. */
protected static Logger log = Logger.getLogger(HibernatePersistenceCustom.class);
#Override
public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map map) {
Ejb3Configuration cfg = new Ejb3Configuration();
//here you can configure it
doCustomConfiguration(cfg);
Ejb3Configuration configured = cfg.configure(info, map);
return configured != null ? configured.buildEntityManagerFactory() : null;
}
...
//other methods can also be overridden if required.
public void doCustomConfiguration(Ejb3Configuration config) {
//Load and process dynamic Mappings.
Iterator classMappingsIter = config.getClassMappings();
while(classMappingsIter.hasNext()) {
PersistentClass persistentClass = (PersistentClass) classMappingsIter.next();
String discriminatorValue = persistentClass.getDiscriminatorValue();
if(discriminatorValue != null) {
log.debug("DiscriminatorValue before [" + discriminatorValue + "]");
//here I replace discriminator values.
//The Discriminator values are coded in the annotations
//as names (words). These words need to be replaced with ids
//previously loaded from the database using jdbc.
//The names are constant in all environments, however the ids are
//are different.
discriminatorValue = StringUtil.replacePlaceholders(discriminatorValue, configs);
persistentClass.setDiscriminatorValue(discriminatorValue);
log.debug("DiscriminatorValue after [" + discriminatorValue + "]");
}
}
}
}
then in persistence.xml instead of org.hibernate.ejb.HibernatePersistence put com.mydomain.persistence.HibernatePersistenceCustom
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="mypersistenceunit" transaction-type="JTA">
<provider>com.mydomain.persistence.HibernatePersistenceCustom</provider>
<jta-data-source>jdbc/mydatasource</jta-data-source>
<properties>
<property name="hibernate.show_sql" value="false"/>
<property name="hibernate.format_sql" value="false"/>
<property name="hibernate.use_sql_comments" value="false"/>
<property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.SunONETransactionManagerLookup"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.Oracle10gDialect"/>
</properties>
</persistence-unit>
</persistence>
Have not tested it yet, but I think it will work.
Thanks
You could override metadata annotations by providing an XML mapping file (see the Chapter 10 XML Descriptor in the JPA 1.0 specification).
Of course, this is not dynamic (unless you generate the XML mapping file using for example FreeMarker and feed values from the database).
Related
Multiple questions ahead.
This code works fine.
I'm working in Eclipse.
As you see from the whole setup, I'm using JTA.
Project layout
Local Project
- servlets
- _Index (using User)
- _Index2 (using Person)
- cruds
- UserCRUD
- PersonCRUD
- entities
- User
Remote/Other/Library Project
- entities
- Person
persistence.xml:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="default" transaction-type="JTA">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<jta-data-source>jdbc/test</jta-data-source>
<class>tests.jee.simple.entities.User</class>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="eclipselink.ddl-generation" value="create-or-extend-tables" />
<property name="javax.persistence.schema-generation.database.action" value="create" />
<property name="javax.persistence.schema-generation.scripts.action" value="create" />
<property name="javax.persistence.schema-generation.create-source" value="metadata-then-script" />
<property name="javax.persistence.schema-generation.drop-source" value="script-then-metadata" />
</properties>
</persistence-unit>
</persistence>
Servlet:
#WebServlet("/")
public class _Index extends HttpServlet {
private static final long serialVersionUID = -2039252988955365999L;
#EJB private UserCRUD mUserCRUD;
#Override protected void doGet(final HttpServletRequest pReq, final HttpServletResponse pResp) throws IOException {
final User u = new User();
u.setName("User #" + ((int) (Math.random() * 1000)));
mUserCRUD.persist(u);
pResp.getWriter().write("<pre>");
final List<User> all = mUserCRUD.getAll();
for (final User user : all) {
pResp.getWriter().write("\t" + user + "\r\n");
}
pResp.getWriter().write("</pre>");
}
}
Entity:
#Entity
public class User {
#Id #GeneratedValue private long id;
private String name;
public String getName() {
return name;
}
public void setName(final String pName) {
name = pName;
}
#Override public String toString() {
return "User [id=" + id + ", name=" + name + "]";
}
}
CRUD/DAO/EJB:
#Stateless
public class UserCRUD {
#PersistenceContext private EntityManager mEM;
public void persist(final User pUser) {
mEM.persist(pUser);
}
public List<User> getAll() {
final TypedQuery<User> query = mEM.createQuery("SELECT i FROM " + User.class.getSimpleName() + " i", User.class);
return query.getResultList();
}
}
Now I want to use Entities from other Eclipse projects.
Class Person in other Project
#Entity
#Table(name = "Company_Person")
public class Person {
#Id #GeneratedValue private long mId;
private String mName;
public Person() {}
public void setName(final String pName) {
mName = pName;
}
public String getName() {
return mName;
}
}
And CRUD in local project, right next to UserCRUD:
#Stateless
public class PersonCRUD {
#PersistenceContext private EntityManager mEM;
public void persist(final Person pUser) {
mEM.persist(pUser);
}
public List<Person> getAll() {
final TypedQuery<Person> query = mEM.createQuery("SELECT i FROM " + Person.class.getSimpleName() + " i", Person.class);
return query.getResultList();
}
}
Servlet #2 in local project
#WebServlet("/2")
public class _Index2 extends HttpServlet {
private static final long serialVersionUID = -2039252988955365999L;
#EJB private PersonCRUD mPersonCRUD;
#Override protected void doGet(final HttpServletRequest pReq, final HttpServletResponse pResp) throws IOException {
final Person u = new Person();
u.setName("User #" + ((int) (Math.random() * 1000)));
mPersonCRUD.persist(u);
pResp.getWriter().write("<pre>");
final List<Person> all = mPersonCRUD.getAll();
for (final Person user : all) {
pResp.getWriter().write("\t" + user + "\r\n");
}
pResp.getWriter().write("</pre>");
}
}
But as soon as I start referencing classes from other projects (including then in the build path), JPA goes weird:
First, it tells me on deployment, that JNDI lookup failed for the additional CRUD:
2018-03-23T17:43:41.835+0100|Severe: Exception while deploying the app [Test_JEE_Simple] : JNDI lookup failed for the resource: Name: [java:module/env/tests.jee.simple.servlets._Index2/mPersonCRUD], Lookup: [tests.jee.simple.crud.PersonCRUD#tests.jee.simple.crud.PersonCRUD], Type: [Session]
javax.naming.NamingException: Lookup failed for 'tests.jee.simple.crud.PersonCRUD#tests.jee.simple.crud.PersonCRUD' in SerialContext[myEnv={java.naming.factory.initial=com.sun.enterprise.naming.impl.SerialInitContextFactory, java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl, java.naming.factory.url.pkgs=com.sun.enterprise.naming} [Root exception is javax.naming.NameNotFoundException: tests.jee.simple.crud.PersonCRUD#tests.jee.simple.crud.PersonCRUD not found]
JPA does not automatically find classes from other projects, throws me ClassNotFoundExceptions.
So I add the related project to Project->Properties->Deployment Assembly.
Accessing the users (/Test_JEE_Simple/ => _Index.class) works fine.
Accessing the users (/Test_JEE_Simple/2 => _Index2.class) does not work, throws
Caused by: java.lang.IllegalArgumentException: Object: User #706 (null/null) is not a known Entity type.
When I try adding Person.class to the persistence.xml entries, still same problem.
So here my questions:
Is it possible by only using JTA, or do I have to go RESOURCE_LOCAL at some point?
Is there a way to also have the PersonCRUD in the other project?
Usually I use a crud base class, and if need be, the specific classes - like UserCRUD and PersonCRUD - extend it. Works just fine. Don't wanna write loads of boilerplate code, but with that approach still can handle specific requests. So: Would it even be possible to have that generic CRUD-Class in another project?
Is there any way (annotations etc) to use the JTA's EntityManager Transaction? I saw a trick somewhere, but didn't test and save it, so it's lost to me...
UPDATE:
For some magic reason (and there seems to be a lot of magic) I can now use the Entities from other classes... Happened sometime after I added the other Project in local projects Deployment Assembly, undeployed, cleaned, closed, reopened and deployed the local project...
So the questions now remaining are 2-4, desired layout:
Local Project (referencing lib #1 and lib #2)
- servlets
- _Index (using User)
- _Index2 (using Person)
- cruds
- UserCRUD: CrudBase<User>
- entities
- User
Remote/Other/Library Project #1 (referencing lib #2)
- entities
- Person
- cruds
- PersonCRUD: CrudBase<Person>
Remote/Other/Library Project #2 (super-library)
- cruds
- CrudBase<T>
I've been searching a way to make envers not recording any entity that I merged when there were no modification since last record.
I turns out that this should be Envers' normal behavior (no audit if there are no modifications).
Entities only have the #Audited annotations, but they keep being audited even when there is no change since last audit.
This is the configuration of my persitence.xml:
<property name="org.hibernate.envers.revision_field_name" value="revision" />
<property name="org.hibernate.envers.revision_type_field_name" value="revision_type" />
<property name="org.hibernate.envers.revision_on_collection_change" value="false"/>
<property name="org.hibernate.envers.store_data_at_delete" value="true"/>
I have found this Hibernate Envers: Auditing an object, calling merge on it, gives an audit record EVERY time even with no change? but there is no answer.
Some of my equals()/hascode() methods are only testing for IDs (the primary keys), but I didn't find out any topic on how this could be related.
I'v also seen taht there is a new parameter to see which field changed, but I don't think that's related to my problem too.
I'm using Postgresql, if that matters.
Any ideas for this behavior ? The only solution I have for the moment is to get the entity through the entityManager and compare them (I'll use some reflection based API if it comes to this).
The problem wasn't from the application, but from the code itself. Our entites has a field "lastUpdateDate", which was set at the current date on every merge(). The comparaison is done by envers after the merge, so this field has change since last revision.
For those who are curious, changes between versions are evaluated in org.hibernate.envers.internal.entities.mapper.MultiPropertyMapper.map() (at least on evers 4.3.5.Final) which returns true if there are any changes between oldState and newState. It uses a specific mapper depending on the property compared.
EDIT: I'll put here how I solved the problem, but Dagmar's solution can also be used. Mine might be a little bit trickier and dirtier however.
I used Envers's EnversPostUpdateEventListenerImpl as describerd in The official documentation and various SO answers: I created mine and forced Envers to use it.
#Override
public void onPostUpdate(PostUpdateEvent event) {
//Maybe you should try catch that !
if ( event.getOldState() != null ) {
final EntityPersister entityPersister = event.getPersister();
final String[] propertiesNames = entityPersister.getPropertyNames();
for ( int i = 0; i < propertiesNames.length; ++i ) {
String propertyName = propertiesNames[i];
if(checkProperty(propertyName){
event.getOldState()[i] = event.getState()[i];
}
}
// Normal Envers processing
super.onPostUpdate(event);
}
My checkProperty(String propertyName) just checked if it was an update date property (propertyName.endsWith("lastUpdateDate") because that's how they are in our app). The trick is, I set the old state to the new state so if that's the only modified field in my entity, it won't audit it (persist it with envers). But if there are other fields which where modified, Envers will audit the entity with those modified fields and with the right lastUpdateDate.
I also had a problem where oldState was time with hh:mm:ss not set (only zero's) and the new state was the same day with hours set. So I used a similar trick:
Date oldDtEffet = (Date) event.getOldState()[i];
Date newDtEffet = (Date) event.getState()[i];
if(oldDtEffet != null && newDtEffet != null &&
DateUtils.isDateEqualsWithoutTime(oldDtEffet,newDtEffet)){
event.getOldState()[i] = event.getState()[i];
}
(Note: you must reimplement ALL event listeners, even though they will juste inherit Envers classes, there's no turnaround. Be sure that the org.hibernate.integrator.spi.Integrator is in your application)
The good news is that Hibernate Envers works as expected - versions (entries into the AUD tables) are not created unless an auditable property is modified.
However, in our application we had implemented a MergeEventListener which was updating tracking fields (lastUpdated, lastUpdatedBy) on every entity save. This caused Envers to make a new version even when there were no changes to the entity.
The solution was quite simple in the end (for us) - using an example of how to use Interceptors and Events from Hibernate: http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/events.html
We replaced our class implementing PersistEventListener and MergeEventListener with a class that extends EmptyInterceptor and overrides the onFlushDirty and onSave methods.
public class EntitySaveInterceptor extends EmptyInterceptor {
#Override
public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {
setModificationTrackerProperties(entity);
return super.onFlushDirty(entity, id, currentState, previousState, propertyNames, types);
}
#Override
public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
setModificationTrackerProperties(entity);
return super.onSave(entity, id, state, propertyNames, types);
}
private void setModificationTrackerProperties(Object object) {
if (SecurityContextHolder.getContext() != null && SecurityContextHolder.getContext().getAuthentication() != null) {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal != null && principal instanceof MyApplicationUserDetails) {
User user = ((MyApplicationUserDetails) principal).getUser();
if (object instanceof ModificationTracker && user != null) {
ModificationTracker entity = (ModificationTracker) object;
Date currentDateTime = new Date();
if (entity.getCreatedDate() == null) {
entity.setCreatedDate(currentDateTime);
}
if (entity.getCreatedBy() == null) {
entity.setCreatedBy(user);
}
entity.setLastUpdated(currentDateTime);
entity.setLastUpdatedBy(user);
}
}
}
}
}
Hooking up the EntitySaveInterceptor to the Hibernate JPA persistence unit
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="myapplication" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="hibernate.ejb.interceptor" value="org.myapplication.interceptor.EntitySaveInterceptor" />
<property name="hibernate.hbm2ddl.auto" value="none"/>
<property name="hibernate.show_sql" value="false"/>
</properties>
</persistence-unit>
</persistence>
And for completeness, here is the ModificationTracker interface:
public interface ModificationTracker {
public Date getLastUpdated();
public Date getCreatedDate();
public User getCreatedBy();
public User getLastUpdatedBy();
public void setLastUpdated(Date lastUpdated);
public void setCreatedDate(Date createdDate);
public void setCreatedBy(User createdBy);
public void setLastUpdatedBy(User lastUpdatedBy);
}
It should also be possible to solve this problem by using an implementation of PreUpdateEventListener to set the ModificationTracker values because that listener is also only fired when the object is dirty.
I had similar situation.
I found out that the reason for duplicate rows in audit tables was usage of LocalDateTime field in the audited entity.
LocalDateTime field is persisted to DATETIME field in MySQL database. The problem was that DATETIME field has precision of 1 second, while LocalDateTime has much higher precision, so when Envers compares the data from the database to the object it sees the difference, even the LocalDateTime field hasn't been changed.
I solved this by truncating LocalDateTime field to seconds.
There must be a bunch of questions regarding this, and I have read a few, but the answer still eludes me. I am new to JPA and I am just trying to test a simple application to see if I can configure the thing properly. It is a stand alone application meaning it will not be run with a web server or anything. The entity class looks like:
#Entity
public class Person{
#Id
private String userID = null;
#Transient
private UserState userState = null;
private String email = null;
private String name = null;
public Person(){
userID = null;
email = null;
name = null;
userState = null;
}
public String getUserID() {
return userID;
}
public void setUserID(String userID) {
this.userID = userID;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public UserState getUserState() {
return userState;
}
public void setUserState(UserState userState) {
this.userState = userState;
}
}
The main:
public class PersistenceTest {
public static void main(String[] args) {
System.out.println("creating person");
Person p = new Person();
p.setUserID("GregR");
p.setEmail("Gregory#company.de");
p.setUserState(UserState.ACTIVE);
System.out.println("done creating person GregR");
EntityManagerFactory factory = Persistence.createEntityManagerFactory(PersonService.PERSISTENCE_UNIT_NAME);
System.out.println("factory initialized");
EntityManager manager = factory.createEntityManager();
System.out.println("EntityManager initialized");
PersonService service = new PersonService(manager);
System.out.println("service initialized");
System.out.println("Beginning transaction");
manager.getTransaction().begin();
System.out.println("Transaction begun");
System.out.println("attempting to persist person");
service.persistEntity(p);
manager.getTransaction().commit();
System.out.println("person persisted");
System.out.println("beginning cleanup");
manager.close();
factory.close();
System.out.println("Cleanup has completed");
}
}
The config:
<?xml version="1.0"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<persistence-unit name="PersonService" transaction-type="RESOURCE_LOCAL">
<provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
<class>de.hol.persistable.entities.Person</class>
<properties>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/ConnectionWikisDB"/>
<property name="javax.persistence.jdbc.user" value="GregR"/>
<property name="javax.persistence.jdbc.password" value="myPassword"/>
</properties>
</persistence-unit>
</persistence>
The console printout:
creating person
done creating person GReeder
factory initialized
47 PersonService INFO [main] openjpa.Runtime - Starting OpenJPA 2.3.0
110 PersonService INFO [main] openjpa.jdbc.JDBC - Using dictionary class "org.apache.openjpa.jdbc.sql.MySQLDictionary".
306 PersonService INFO [main] openjpa.jdbc.JDBC - Connected to MySQL version 5.5 using JDBC driver MySQL-AB JDBC Driver version mysql-connector-java-5.0.8 ( Revision: ${svn.Revision} ).
Exception in thread "main" <openjpa-2.3.0-r422266:1540826 nonfatal user error> org.apache.openjpa.persistence.ArgumentException: This configuration disallows runtime optimization, but the following listed types were not enhanced at build time or at class load time with a javaagent: "
de.hol.persistable.entities.Person".
at org.apache.openjpa.enhance.ManagedClassSubclasser.prepareUnenhancedClasses(ManagedClassSubclasser.java:115)
at org.apache.openjpa.kernel.AbstractBrokerFactory.loadPersistentTypes(AbstractBrokerFactory.java:312)
at org.apache.openjpa.kernel.AbstractBrokerFactory.initializeBroker(AbstractBrokerFactory.java:236)
at org.apache.openjpa.kernel.AbstractBrokerFactory.newBroker(AbstractBrokerFactory.java:212)
at org.apache.openjpa.kernel.DelegatingBrokerFactory.newBroker(DelegatingBrokerFactory.java:155)
at org.apache.openjpa.persistence.EntityManagerFactoryImpl.createEntityManager(EntityManagerFactoryImpl.java:226)
at org.apache.openjpa.persistence.EntityManagerFactoryImpl.createEntityManager(EntityManagerFactoryImpl.java:153)
at org.apache.openjpa.persistence.EntityManagerFactoryImpl.createEntityManager(EntityManagerFactoryImpl.java:59)
at de.hol.persistable.PersistenceTest.main(PersistenceTest.java:24)
My Questions
1. I guess the main question is, what am I doing wrong. I am very new to this and am trying to just get this stand alone application to work so that I can expand it for real world use.
2. Am I missing some other configuration other than the persistence.xml file?
3. What is the simplest way of getting around this error for a stand-alone app?
Many thanks in advance!
I see you have main class, therefore I assume you are using it in Java SE environment. The easiest way to make it work is to define -javaagent in command line, like this:
java -jar myJAR.jar -javaagent:openjpa-all-2.3.0.jar
It is also possible from Eclipse: Run->Run Configurations->find your application in "Java Applications"->Arguments->VM arguments->add
-javaagent:/full/path/to/openjpa-all-2.3.0.jar
By inspecting [ManagedClassSubclasser]1 code you can see that this is more like a warning. If you want to silence this exception and log a warning instead by:
<property name="openjpa.RuntimeUnenhancedClasses" value="warn"/>
I would like to add my experience while faced same kind of problem. If you are usng the tomcat it doesn't implement the full spec for OpenJPA you need to provide the enhancements separately. For the enhancement follow the instructions available on this link http://openjpa.apache.org/enhancement-with-eclipse.html. And you will be good to go.
NOTE: i know you are not using any server, but in case you want to use tomcat then you need to follow the link.
I have a Jax RS server which is correctly called when a POST request is sent with a Movie in XML format.
#Resource(name = "movie")
#Path("/movie")
public class MovieResource
{
#PersistenceContext(unitName = "movieDS")
private EntityManager em;
public MovieResource()
{
em = PersistenceProvider.createEntityManager();
}
#POST
#Path("/post")
#Consumes(
{
MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML
})
public Response createMovie(Movie movie)
{
if (!em.contains(newMovie))
{
em.merge(newMovie);
}
String result = "Movie created : " + movie;
return Response.status(201).entity(movie).build();
}
}
debugging shows no errors whatsoever however nothing is persisted.
The datasource is JTA over EclipseLink, here is persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<!-- The data source should be set up in Glassfish -->
<persistence-unit name="MovieManager" transaction-type="JTA">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<jta-data-source>jdbc/movieDS</jta-data-source>
<properties>
<property name="eclipselink.logging.level" value="ALL"/>
<property name="eclipselink.ddl-generation" value="create-tables"/>
</properties>
</persistence-unit>
</persistence>
The logs returned from EclipseLink show no error whatsoever while calling em.merge(), they mostly involve sequence creation:
FINEST: Execute query ValueReadQuery(name="SEQUENCE" sql="SELECT SEQ_COUNT FROM SEQUENCE WHERE SEQ_NAME = #SEQ_NAME")
FINE: SELECT SEQ_COUNT FROM SEQUENCE WHERE SEQ_NAME = ?
bind => [1 parameter bound]
FINEST: local sequencing preallocation for SEQ_GEN: objects: 50 , first: 51, last: 100
INFO: RAR7115: Unable to set ClientInfo for connection
FINEST: Connection released to connection pool [default].
FINER: TX commitTransaction, status=STATUS_ACTIVE
FINER: TX Internally committing
FINEST: local sequencing preallocation is copied to preallocation after transaction commit
FINER: external transaction has committed internally
FINEST: assign sequence to the object (51 -> net.plarz.entities.LoginInformation#91229c)
Has anyone any idea what's missing? the Movie class is very simple and has no dependencies with other tables, I think its something really simple I'm missing.
EDIT :
If I add a flush() after merge() I get an error:
javax.persistence.TransactionRequiredException:
Exception Description: No externally managed transaction is currently active for this thread
You shouldn't call flush() but instead create an enterprise bean so:
#Stateless
public class MovieEJB
{
#PersistenceContext(unitName = "movieDS")
private EntityManager em;
#Override
public Movie create(Movie movie) throws Exception
{
em.persist(movie);
return movie;
}
#Override
public void delete(Movie movie)
{
em.remove(em.merge(movie));
}
#Override
public Movie update(Movie movie) throws Exception
{
return em.merge(movie);
}
}
then modify your MovieResource class so:
#ManagedBean(name = "restController")
#SessionScoped
#Resource(name = "movie")
#Path("/movie")
public class MovieResource
{
#EJB
private MovieEJB movieEJB;
public MovieResource()
{
}
public MovieEJBLocal getMovieEJB()
{
return movieEJB;
}
public void setMovieEJB(MovieEJBLocal movieEJB)
{
this.movieEJB = movieEJB;
}
#POST
#Path("/post")
#Consumes(
{
MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML
})
public Response createMovie(Movie movie)
{
getMovieEJB().create(movie);
String result = "Movie created : " + movie;
return Response.status(201).entity(movie).build();
}
}
You are using a JTA persistence unit, but not starting a JTA transaction.
Either switch to RESOURCE_LOCAL and non-JTA DataSource, or use a JTA transaction such as using an EJB (see other answer).
In my project we'd like to externalize the properties of our Spring managed beans, that is very easy to do with standard Java .properties files, however we want to be able to read those properties from a DB table that behaves like a Map (key is the property name, value is the value assigned to that property).
I found this post that suggest the usage of Commons Configuration but I don't know if there's a better way to do the same with Spring 3.x. Maybe implementing my own PropertyResource or something.
Any clues?
I'd use a FactoryBean of type <Properties> that I'd implement using JdbcTemplate. You can then use the generated Properties object with the <context:property-placeholder> mechanism.
Sample code:
public class JdbcPropertiesFactoryBean
extends AbstractFactoryBean<Properties>{
#Required
public void setJdbcTemplate(final JdbcTemplate jdbcTemplate){
this.jdbcTemplate = jdbcTemplate;
}
private JdbcTemplate jdbcTemplate;
#Required
public void setTableName(final String tableName){
this.tableName = tableName;
}
private String tableName;
#Required
public void setKeyColumn(final String keyColumn){
this.keyColumn = keyColumn;
}
private String keyColumn;
#Required
public void setValueColumn(final String valueColumn){
this.valueColumn = valueColumn;
}
private String valueColumn;
#Override
public Class<?> getObjectType(){
return Properties.class;
}
#Override
protected Properties createInstance() throws Exception{
final Properties props = new Properties();
jdbcTemplate.query("Select " + keyColumn + ", " + valueColumn
+ " from " + tableName, new RowCallbackHandler(){
#Override
public void processRow(final ResultSet rs) throws SQLException{
props.put(rs.getString(1), rs.getString(2));
}
});
return props;
}
}
XML Configuration:
<bean id="props" class="foo.bar.JdbcPropertiesFactoryBean">
<property name="jdbcTemplate">
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<!-- reference to a defined data source -->
<constructor-arg ref="dataSource" />
</bean>
</property>
<property name="tableName" value="TBL_PROPERTIES" />
<property name="keyColumn" value="COL_KEY" />
<property name="valueColumn" value="COL_VAL" />
</bean>
<context:property-placeholder properties-ref="props" />
In addition to Sean's suggestion, you can extend PropertyPlaceholderConfigurer. Look at the two current implementations - PreferencesX and ServletContextX, and roll out your own, jdbc-based.
There are ways to create "PropertyPlaceholderConfigurer" Programmatically , please see below.
Write a DAO which reads Properties and create a PropertyPlaceholderConfigurer as shown below.
XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource("beans.xml"));
PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
cfg.setProperties(yourProperties);
cfg.postProcessBeanFactory(factory);