Reputation: 2530
...at least, if the concept of an "elegant workaround" actually has merit!
Here are some details:
When using CDI and JPA, you'll often want to access one of your JPA-managed beans in an EL Expression on one of your JSF pages, like so:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>An Everyday Facelet</title>
</h:head>
<h:body>
<h1>Behold, a jpa-managed bean property!</h1>
<h:outputText value="#{exampleEntity.name}" />
</h:body>
</html>
For simplicity, we'll assume that this page is the result of an HTTP Post and that the client entered a value for the name property on the previous page. The bean itself can be a simple jpa entity retrieved from a producer method, like so:
@Entity
public class ExampleEntity implements Serializable {
@Id
@Column
private Long entityId;
@Column
private String name;
public ExampleEntity() {
}
public void setEntityId(Long entityId) {
this.entityId = entityId;
}
public Long getEntityId() {
return entityId;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
// Some method in another class
public class AnotherClass {
@Produces
@RequestScoped
@Named("exampleEntity")
public ExampleEntity getExampleEntity() {
return new ExampleEntity();
}
}
All right, let's compile. One important aspect of the datanucleus JPA implementation is that there is a post-compile step. You need to run a bytecode enhancer on your compiled jpa classes, so let's assume we did that. When trying to boot your servlet container (ie, Tomcat, Jetty) you'll get an exception when starting Weld (more than one if you actually try to inject this bean somewhere). Assuming my classes are in the 'testapp' package, the exception is something like:
org.jboss.weld.exceptions.UnproxyableResolutionException: WELD-001437 Normal scoped bean class testapp.ExampleEntity is not proxyable because the type is final or it contains a final method public final java.lang.Object testapp.ExampleEntity.jdoGetObjectId() - Managed Bean [class testapp.ExampleEntity] with qualifiers [@Any @Default @Named].
public final java.lang.Object jdoGetObjectId()? I neither defined nor declared one of those, good web server. You must be mistaken!
Turns out the datanucleus enhancer must be adding this final method to my compiled class. The web server starts up fine if I don't run it, although I assume my data wouldn't actually persist. With a little bit of research, I was able to find an explanation of the problem in the Weld documentation: http://docs.jboss.org/weld/reference/1.1.5.Final/en-US/html/injection.html#d0e1429
In a nutshell, it looks like named beans can't have final methods. Following some of the advice there, I found a working solution. I declare an interface for my jpa bean, and create a producer method for obtaining a concrete class (which is actually the datanucleus enhanced class, but Weld doesn't know). Here's the additional code:
public interface ExampleEntity {
public Long getEntityId();
public void setEntityId(Long entityId);
public String getName();
public void setName(String name);
}
Now that ExampleEntity is purely an interface, I create a JPA-managed implementation without CDI annotations
@Entity
public class ExampleEntityImpl implements ExampleEntity, Serializable {
// Same as the ExampleEntity class above
}
And finally, perhaps in a controller somewhere, I define a producer method for getting ExampleEntities
@Named("exampleEntityController")
@RequestScoped
public class ExampleEntityController {
private ExampleEntity newExampleEntity;
public ExampleEntityController() {
newExampleEntity = new ExampleEntityImpl();
}
// The producer method
@Produces
@RequestScoped
@Named("exampleEntity")
public ExampleEntity getExampleEntity() {
return newExampleEntity;
}
// Other stuff, because surely a controller does more than just this, right?
}
And... this works. The web server starts, no exceptions. The name attribute is properly retrieved in that example facelet (assuming data was already entered in a previous page). However, making one-off interfaces and producer methods for every jpa-managed bean is pretty redundant and ugly. Is there a better solution? Datanucleus is a requirement for this project, so I cannot simply use something else. If there is a more "elegant workaround," I imagine it involves a better way to get Weld to let me have a final method that I'll never use in one of my named beans.
Edit: Thanks for the responses/corrections, but I'm looking for a way to avoid having to create an interface for each jpa bean. I've removed mixing JPA/CDI annotations from the examples, but the web server still throws the same exception regardless. I can see that I'll need a producer method either way, though.
Upvotes: 2
Views: 724
Reputation: 597134
You should not use CDI for your entities.
new
operatorUpvotes: 1
Reputation: 5378
Do not mix CDI annotations with JPA. What you really need to do is to create a producer for any entity you want to use with CDI. JPA needs to create the instance, then a producer would allow it to be used in a CDI environment or named for EL usage.
Upvotes: 1