I've implemented a REST API based on JPA and JAXB.
I have a classes roughly like this (very simplified):
#Entity
#XmlRootElement
...
public class Thing {
#Id
#GeneratedValue
...
#XmlAttribute
#XmlID
#XmlJavaTypeAdapter(JAXBLongAdapter.class)
private Long id;
...
}
Hibernate (my current JPA provider) generates numbers as the id value, but they are naturally unique only for one one type, Thing in this example.
Now XSD says that xsd:id (#XmlID) is a NCString which cannot be a plain number so i prepended a '_' to numbers in the JAXBLongAdapter. - like '_1'
Now the schema validator complains:
[org.xml.sax.SAXParseException: cvc-id.2: There are multiple occurrences of ID value '_1'.]
If I understand this correctly a xsd:ID element must have a (string) value that is globally unique in the xml document. But this is very opposite of the common way of using IDs in databases.
What do I do now?
I thought of three things:
Create a JAXBLongAdapter for each type with a type specific prefix?
Using another JPA id generator, perhaps UUID? - But which one?
Stop using #XmlID and #XmlIDREF, which creates redundancy and general messiness.
It seems that I now have to change the Database schema to use different IDs.
- But it would be nice if the IDs stayed short, because they appear in URLs.
My question: Is there a ID generator that is comparably fast and is globally unique?
Or is there another way of tackling this?
EDIT:
This hack kinda works, leaving the JPA IDs intact.
#XmlID
#XmlAttribute(name="id")
private String getXmlID(){
return String.format("%s-%s", this.getClass().getSimpleName(), this.getId().toString());
}
private void setXmlID(String xmlid){
String prefix = String.format("%s-", this.getClass().getSimpleName());
if(xmlid.startsWith(prefix)){
this.id = Long.parseLong(xmlid.substring(prefix.length()));
}else{
throw new IllegalArgumentException(xmlid+" does not look like "+prefix+"###");
}
}
By moving the JAXB Annotation from the field to dedicated private getters/setters for the XmlID.
That's exactly what I had done with for some time.
You can ask yourself what is actually #XmlID for this domain object when marshalled?
I once thought #XmlID and #XmlIDREF can solve the circular problem in JAXB.
Here comes what I'm doing with my JPA entities along with JAXB annotations.
Do not give the simple JPA #Id up. That's the heart of JPA.
#XmlRootElement
public class Parent {
#Id
#XmlAttribute
private Long id;
#OneToMany
#XmlElement(name = "child")
#XmlElementWrapper
private Collection<Child> children;
}
#XmlRootElement
public class Child {
#XmlAttribute
private Long getParentId() {
return parent.getId();
}
#Id
#XmlAttribute
private Long id;
#ManyToOne
#XmlTransient // for preventing infinite circular problem
private Parent parent;
}
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
WHAT THE ANSWER SHOULD BE
The answer should be to use the #XmlSchemaType annotation on both the #XmlID and #XmlIDREF properties. Unfortunately the JAXB RI does not leverage that combination, and EclipseLink MOXy only leverages it for #XmlID. I have entered the following MOXy bug, which we could fix if you are interested in this approach:
http://bugs.eclipse.org/386569
Employee
package forum11791735;
import java.util.List;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Employee {
#XmlID
#XmlJavaTypeAdapter(JAXBLongAdapter.class)
#XmlSchemaType(name="long")
private Long id;
#XmlIDREF
#XmlSchemaType(name="long")
private Employee manager;
#XmlElement(name="report")
private List<Employee> reports;
}
WORK AROUND
The error you are seeing is appears to be due to schema validation. Is it possible for you to disable schema validation or set a ValidationEventHandler on the Unmarshaller to ignore these errors?
http://blog.bdoughan.com/2010/12/jaxb-and-marshalunmarshal-schema.html
ALTERNATIVES
If you are using #XmlID/#XmlIDREF to map bidirectional relationships then you may be interested in MOXy's #XmlInverseReference extension:
http://blog.bdoughan.com/2010/07/jpa-entities-to-xml-bidirectional.html
Related
I'm trying to use Hibernate Search on two Entities, that do not (and must not) share a relation on object-level, however they're connected by a join table that uses their IDs. (legacy)
These are more or less the two Entities:
#Entity
#Indexed
class Person {
#Id
private long id;
#Field
private String name;
....
}
#Entity
#Indexed
class Address {
#Id
private long id;
#Field
private String street;
#Field
private String zip;
....
}
They are connected by their IDs:
#Entity
class Relation {
#Id
private long id;
private long personId;
private long addressId;
}
The goal I'm trying to achieve is finding similar persons that share a similar address via Hibernate Search. This means I'm searching for attributes from both Person and Address.
I guess the easiest way is to "emulate" an #IndexedEmbedded relation which means denormalizing the data and add "street" and "zip" from Address to a Person document. I stumbled upon Hibernate Search Programmatic API, but I'm not sure if that's the right way to go (and how to go on from to there)..
Would this be the proper way of doing things or am I missing something?
If you cannot add this relationship into the model, you will be pretty much out of luck. You are right that you would have to index the Person and corresponding Address data into the same document (this is what #IndexedEmbedded does really). The normal/best way to customize the Document is via a custom (class) bridge. The problem in your case, however, is that you would need access to the current Hibernate Session within the implementation of the custom bridge.
Unless you are using some approach where this Session for example is bound to a ThreadLocal, there won't be a way for you to load the matching Address data for a given Person within the bridge implementation.
when I use the JPA #Version annotaton in an #Embeddable I get the following exception pointing at my Updateable class:
org.hibernate.AnnotationException: Unable to define #Version on an embedded class
Here is my code:
#Embeddable
public class Updateable {
#Version
private long modcount;
private String updatedBy;
private DateTime updatedAt;
// getters & setters
}
#Entity
public class SomeEntity {
#Id
private Long id;
#Embedded
private Updateable updateAudit;
// other stuff
}
Is it not possible to have a #Version in an #Embeddable, or is this Hibernate specific?
An embeddable class is just a convinience way of declaring reusable entity elements, i.e. your Updateable could be used in other entities without having to add the fields and the mapping again.
As such, embeddables are part of the entity (as the name suggests they are embedded) and thus independent versioning doesn't make sense.
Adding the #Version annotation to the embeddable only would also not make much sense since the embeddable itself can't be versioned and you'd have to deal with cases where multiple embeddables are contained in a single entity (e.g. which version should be used in that case?). So since #Version only makes sense for entities it's easier to just allow that annotation for entities or mapped superclasses.
Actually although the JPA spec recommends that version properties are numeric, strings or timestamps Hibernate seems to provide user defined version types:
The version column may be a numeric (the recommended solution) or a timestamp. Hibernate supports any kind of type provided that you define and implement the appropriate UserVersionType.
So what you might be able to do (not tested, just derived from the docs) if you want to use Updateable as your version is to provide a custom user type for Updateable and then use it like this:
#Entity
public class SomeEntity {
#Id
private Long id;
#Type( "your.custom.UserVersionType" )
#Version
private Updateable updateAudit;
// other stuff
}
I'm developing an Java-application which stores its data via Hibernate in a database.
One feature of this application is to define templates like types, etc. for reuse. For instance the type has attributes and you can create instances of an type, which has values for the attributes.
The problem is, that I don't know how to ensure that only values for attributes can assigned which the type defines. In my solution there is a redundancy which cause the problem, but I don't know how to remove it.
My current (and problematic) approach looks like this:
#Entity
class Type
{
#Id
#Generated
private Long id;
#OneToMany(mappedBy="type")
private List<Attribute> attributes;
//...
}
#Entity
class Attribute
{
#Id
#Generated
private Long id;
#ManyToOne
private Type type;
//...
}
#Entity
class Instance
{
#Id
#Generated
private Long id;
#ManyToOne
private Type type;
//...
}
#Entity
class AttributeValue
{
#Id
#Embedded
private ResourceAttributValueId id;
#Column(name="val")
private String value;
//...
}
#Embeddable
public class ResourceAttributValueId implements Serializable
{
#ManyToOne
private ResourceStateImpl resource;
#ManyToOne
private ResourceAttributeImpl attribute;
//...
}
There the definition of the type is redundant: Type can be reached via AttributeValue->Attribute->Type and AttributeValue->Instance->Type
Another idea was to use type + attribute name as id of the attribute and instance + attribute name as id of the attribute value, but that doesn't solves my problem.
The key for correctly modeling "diamond-shaped" dependencies like this is the usage of identifying relationships:
(I took a liberty of renaming your entities slightly, to what I believe is a more consistent naming scheme.)
Note how we migrate the TYPE_ID from the top of the diamond, down both sides, all the way to the bottom and then merge it there. So, since there is only one ATTRIBUTE_INSTANCE.TYPE_ID field and is involved in both FKs, we can never have an attribute instance whose attribute type's type differs from instance's type.
While this avoids "mismatched" attributes, it still doesn't ensure the presence of attribute instances (if you support the concept of "required attribute"), which is best enforced at the application level. Theoretically you could enforce it at the database level, using circular deferred FKs, but not all DBMSes support that, and I doubt it would play nicely with ORMs.
Unfortunately, I'm not experienced enough with Hibernate to answer whether this can be mapped there and how.
See also:
Choosing from multiple candidate keys
How to keep foreign key relations consistent in a “diamond-shaped” system of relationships
I am trying to use the CycleRecoverable interface to manage cyclic issues in my object model for bi-directional relationships. Guides such as this and this, tell you to use CycleRecoverable, but I don't have it on my class path. I'm confused as to what I actually need, and an explanation as to why I need it. I am not using Maven, so I can't follow the second links advice, and even if I was using Maven, I don't understand why I need more packages in order to use JAXB which I thought was included in SE6. What jar files do I actually need to include to use this interface and why? The only CycleRecoverable on my classpath is com.sun.xml.internal.bind.CycleRecoverable
The (un)official JAXB guide notates the interface, but makes no mention of how to actually use it
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB 2 (JSR-222) expert group.
As an alternative to the CycleRecoverable mechanism in the JAXB reference implementation you may be interested in the #XmlInverseReference extension in MOXy:
Customer
import javax.persistence.*;
#Entity
public class Customer {
#Id
private long id;
#OneToOne(mappedBy="customer", cascade={CascadeType.ALL})
private Address address;
}
Address
The #XmlInverseReference annotation specifies the field name that maps the other direction of this relationship. This is similar to how bidirectional relationships are mapped in JPA.
import javax.persistence.*;
import org.eclipse.persistence.oxm.annotations.*;
#Entity
public class Address implements Serializable {
#Id
private long id;
#OneToOne
#JoinColumn(name="ID")
#MapsId
#XmlInverseReference(mappedBy="address")
private Customer customer;
}
For More Information
http://blog.bdoughan.com/2010/07/jpa-entities-to-xml-bidirectional.html
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html
I wanted to know if there is a way to get in a One2Many relationship a field of the One side that is an aggregate of the Many side.
Let's take the following example:
#Entity
public class A {
#Id
private Long id;
#OneToMany (mappedBy="parentA")
private Collection<B> allBs;
// Here I don't know how to Map the latest B by date
private B latestB;
// Acceptable would be to have : private Date latestBDate;
}
#Entity
public class B {
#Id
private Long id;
private Date date;
#ManyToOne (targetEntity=A.class)
private A parentA;
}
My question is how can I make the mapping of the field latestB in the A entity object without doing any de-normalization (not keeping in sync the field with triggers/listeners)?
Perhaps this question gives some answers, but really I don't understand how it can work since I still want to be able to fetch all childs objects.
Thanks for reading/helping.
PS: I use hibernate as ORM/JPA provider, so an Hibernate solution can be provided if no JPA solution exists.
PS2: Or just tell me that I should not do this (with arguments of course) ;-)
I use hibernate as ORM/JPA provider, so an Hibernate solution can be provided if no JPA solution exists.
Implementing the acceptable solution (i.e. fetching a Date for the latest B) would be possible using a #Formula.
#Entity
public class A {
#Id
private Long id;
#OneToMany (mappedBy="parentA")
private Collection<B> allBs;
#Formula("(select max(b.some_date) from B b where b.a_id = id)")
private Date latestBDate;
}
References
Hibernate Annotations Reference Guide
2.4.3.1. Formula
Resources
Hibernate Derived Properties - Performance and Portability
See,
http://en.wikibooks.org/wiki/Java_Persistence/Relationships#Filtering.2C_Complex_Joins
Basically JPA does not support this, but some JPA providers do.
You could also,
- Make the variable transient and lazy initialize it from the OneToMany, or just provide a get method that searches the OneToMany.
- Define another foreign key to the latest.
- Remove the relationship and just query for the latest.