DataNucleus JDO primary key with enum - datanucleus

I am attempting to create a primary key for my class using the example at http://www.datanucleus.org/products/accessplatform_4_0/jdo/orm/compound_identity.html as a guide. My primary key consists of 3 fields, 2 Strings and 1 enum.
According to http://www.datanucleus.org/products/accessplatform_4_0/jdo/types.html, an enum should be able to be part of the primary key, but when the enhancer runs, I get the following:
Caused by: org.datanucleus.metadata.InvalidPrimaryKeyException: Class "A" has been specified with an object-id class A$PK which has 2 fields, whereas the class has been defined with 3 fields in the primary key. The number of fields in the objectidClass and the number in the primary key must concur.
Can someone clarify as to whether the BEnum enum (just a standard enum with no extra methods) can be used as a field in the primary key? If not, what would be the suggested workaround? If it can, what am I doing wrong?
The class is defined as:
PersistenceCapable(detachable = "true", identityType = IdentityType.APPLICATION, objectIdClass = A.PK.class)
public final class A implements Serializable {
#PrimaryKey
private String field1;
#PrimaryKey
private BEnum field2;
#PrimaryKey
private String field3;
public static class PK implements Serializable {
private static final long serialVersionUID = 1L;
public String field1;
public BEnum field2;
public String field3;
public PK() {
}
public PK( String s ) {
StringTokenizer token = new StringTokenizer( s, ":" );
this.field1 = token.nextToken();
this.field2 = BEnum.valueOf( token.nextToken() );
this.field3 = token.nextToken();
}
#Override
public String toString() {
return field1 + ":" + field2 + ":" + field3;
}
#Override
public int hashCode() {
int hash = 7;
hash = 67 * hash + Objects.hashCode( this.field1 );
hash = 67 * hash + Objects.hashCode( this.field2 );
hash = 67 * hash + Objects.hashCode( this.field3 );
return hash;
}
#Override
public boolean equals( Object obj ) {
if( obj != null && (obj instanceof PK) ) {
PK that = (PK)obj;
return Objects.equals( this.field1, that.field2 ) && this.field2 == that.field2 && Objects.equals( this.field3, that.field3 );
}
return false;
}
#Persistent
public String getField1() {
return field1;
}
public void setField1(String field1) {
this.field1 = field1;
}
}
The enum is simply:
public enum BEnum {
VALUE1, VALUE2, VALUE3, VALUE4
}

Related

How to use optional attributes in Spring Data / Webflux

I´m trying to work with joins in Spring-Webflux. I have two tables, comments and votes.
My Comment Entity has an attribute named score, which is the calculated number of votes.
The problem is this score isn´t a field inside the database, but at the moment a transient marked field in the Comment Object which is calculated by my application with bad performance.
My goal is to calculate this with a Join and not in my Application.
My Problem is that Spring doesn´t map the score field because of the transient annotation, which is needed because other Operations (patch or update) doesn´t provide this score field.
My Repository looks like this:
#Repository
public interface CommentRepository extends ReactiveCrudRepository<Comment, UUID> {
#Query("SELECT C.*, COALESCE(SUM(v.vote), 0) as score FROM comment c LEFT JOIN vote v ON c.id = v.comment_id WHERE c.room_id = $1 GROUP BY c.id")
Flux<Comment> findByRoomIdWithScore(UUID roomId);
Flux<Comment> findByRoomId(UUID roomId);
#Transactional
Flux<Void> deleteByRoomId(UUID roomId);
}
and my Comment Object is this:
#Table
public class Comment implements Persistable<UUID> {
#Id
private UUID id;
private UUID roomId;
private UUID creatorId;
private String body;
private Timestamp timestamp;
private boolean read;
private boolean favorite;
private int correct;
private boolean ack;
#Transient
private int score;
private String tag;
private String answer;
#Override
public boolean isNew() {
return id == null;
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public UUID getRoomId() {
return roomId;
}
public void setRoomId(UUID roomId) {
this.roomId = roomId;
}
public UUID getCreatorId() {
return creatorId;
}
public void setCreatorId(UUID creatorId) {
this.creatorId = creatorId;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public Timestamp getTimestamp() {
return timestamp;
}
public void setTimestamp(Timestamp timestamp) {
this.timestamp = timestamp;
}
public boolean isRead() {
return read;
}
public void setRead(boolean read) {
this.read = read;
}
public boolean isFavorite() {
return favorite;
}
public void setFavorite(boolean favorite) {
this.favorite = favorite;
}
public int getCorrect() {
return correct;
}
public void setCorrect(int correct) {
this.correct = correct;
}
public boolean isAck() {
return ack;
}
public void setAck(boolean ack) {
this.ack = ack;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public String getTag() {
return tag;
}
public void setTag(String tag) {
this.tag = tag;
}
public String getAnswer() {
return answer;
}
public void setAnswer(String answer) {
this.answer = answer;
}
#Override
public String toString() {
return "Comment{" +
"id='" + id + '\'' +
", roomId='" + roomId + '\'' +
", creatorId='" + creatorId + '\'' +
", body='" + body + '\'' +
", timestamp=" + timestamp +
", read=" + read +
", favorite=" + favorite +
", correct=" + correct +
", ack=" + ack +
", score=" + score +
", tag=" + tag +
", answer=" + answer +
'}';
}
}
I´ve already tried to make another Comment class without the transient annotation, but that doesn´t work because of the used Repository i guess: reactor.core.Exceptions$ErrorCallbackNotImplemented: java.lang.IllegalArgumentException: Property must not be null!
The solution was to create another Class which extends Comment with the non transient score attribute. This could be casted back into a comment and the score is set

Error on using a custom bridge of hibernate-search

I have two entities:
#Indexed
#Entity
#Table(name = "LK_CONTACT_TYPE")
public class ContactTypeEntity {
#Id
#Column(name = "ID")
#DocumentId
Integer id;
#SortableField
#Field(store = Store.YES, bridge = #FieldBridge(impl = ContactTypeComparator.class))
#Column(name = "NAME")
String name;
getter() .. setter()..
}
#Indexed
#Entity
#Table(name = "DIRECTORY")
public class DirectoryEntity {
....
#IndexedEmbedded(prefix = "contactType.", includePaths = {"id", "name"})
#ManyToOne
#JoinColumn(name = "CONTACT_TYPE")
private ContactTypeEntity contactType;
getter() ... setter()...
}
public class ContactTypeComparator implements MetadataProvidingFieldBridge, TwoWayStringBridge {
#Override
public void set(String name, Object value, Document document, LuceneOptions luceneOptions) {
if ( value != null ) {
int ordinal = getOrdinal(value.toString());
luceneOptions.addNumericFieldToDocument(name, ordinal, document);
}
}
#Override
public void configureFieldMetadata(String name, FieldMetadataBuilder builder) {
builder.field(name, FieldType.INTEGER).sortable(true);
}
private int getOrdinal(ContactType value) {
switch( value ) {
case PBX: return 0;
case TEL: return 1;
case GSM: return 2;
case FAX: return 3;
default: return 4;
}
}
#Override
public Object get(String name, Document document) {
return document.get( name );
}
#Override
public String objectToString(Object object) {
return object.toString();
}
}
and Query part:
...
query.setSort(queryBuilder.sort().byScore().andByField("contactType.name").createSort());
query.setProjection(... , "contactType.name",...);
...
I am getting the following error:
java.lang.IllegalStateException: unexpected docvalues type NONE for field 'contactType.name' (expected=NUMERIC). Use UninvertingReader or index with docvalues.
Note: I am using hibernate-search 5.10.
I want to show contactType.name name on UI instead of number.
For more detail
Seems my original suggestion was missing a bit in the set() method, in order to add the docvalues:
#Override
public void set(String name, Object value, Document document, LuceneOptions luceneOptions) {
if ( value != null ) {
int ordinal = getOrdinal(value.toString());
luceneOptions.addNumericFieldToDocument(name, ordinal, document);
// ADD THIS
luceneOptions.addNumericDocValuesFieldToDocument(name, ordinal, document);
}
}
On top of that, if you need to use the field for both sort and projection, I would recommend declaring two fields. Otherwise the projection will return integers, which is not what you want.
So, do this:
#Indexed
#Entity
#Table(name = "LK_CONTACT_TYPE")
public class ContactTypeEntity {
#Id
#Column(name = "ID")
#DocumentId
Integer id;
#SortableField
// CHANGE THESE TWO LINES
#Field(store = Store.YES)
#Field(name = "name_sort", bridge = #FieldBridge(impl = ContactTypeComparator.class))
#Column(name = "NAME")
String name;
getter() .. setter()..
}
#Indexed
#Entity
#Table(name = "DIRECTORY")
public class DirectoryEntity {
....
// CHANGE THIS LINE
#IndexedEmbedded(prefix = "contactType.", includePaths = {"id", "name", "name_sort"})
#ManyToOne
#JoinColumn(name = "CONTACT_TYPE")
private ContactTypeEntity contactType;
getter() ... setter()...
}
public class ContactTypeComparator implements MetadataProvidingFieldBridge, TwoWayStringBridge {
#Override
public void set(String name, Object value, Document document, LuceneOptions luceneOptions) {
if ( value != null ) {
int ordinal = getOrdinal(value.toString());
luceneOptions.addNumericFieldToDocument(name, ordinal, document);
// ADD THIS LINE
luceneOptions.addNumericDocValuesFieldToDocument(name, ordinal, document);
}
}
#Override
public void configureFieldMetadata(String name, FieldMetadataBuilder builder) {
builder.field(name, FieldType.INTEGER).sortable(true);
}
private int getOrdinal(ContactType value) {
switch( value ) {
case PBX: return 0;
case TEL: return 1;
case GSM: return 2;
case FAX: return 3;
default: return 4;
}
}
#Override
public Object get(String name, Document document) {
return document.get( name );
}
#Override
public String objectToString(Object object) {
return object.toString();
}
}
Then query like this:
...
query.setSort(queryBuilder.sort().byScore().andByField("contactType.name_sort").createSort());
query.setProjection(... , "contactType.name",...);
...

JPQL count Parent Objects on Multiple Children Match in OneToMany Relationship

In a JavaEE JPA web application, Feature entity has bidirectional ManyToOne relationship with Patient Entity. I want to write a query to count the number of Patients who have one or more matching criteria features. I use EclipseLink as the Persistence Provider.
For example, I want to count the number of patients who have a feature with 'variableName' = 'Sex' and 'variableData' = 'Female' and another feature with 'variableName' = 'smoking' and 'variableData' = 'yes'.
How can I write a JPQL query to get the count of patients?
After the first answer, I tried this Query does not give any results as expected.
public void querySmokingFemales(){
String j = "select count(f.patient) from Feature f "
+ "where ((f.variableName=:name1 and f.variableData=:data1)"
+ " and "
+ " (f.variableName=:name2 and f.variableData=:data2))";
Map m = new HashMap();
m.put("name1", "sex");
m.put("data1", "female");
m.put("name2", "smoking");
m.put("data2", "yes");
count = getFacade().countByJpql(j, m);
}
The Patient entity is as follows.
#Entity
public class Patient implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(mappedBy = "patient")
private List<Feature> features;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Patient)) {
return false;
}
Patient other = (Patient) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
#Override
public String toString() {
return "entity.Patient[ id=" + id + " ]";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Feature> getFeatures() {
return features;
}
public void setFeatures(List<Feature> features) {
this.features = features;
}
}
This is the Feature Entity.
#Entity
public class Feature implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String variableName;
private String variableData;
#ManyToOne
private Patient patient;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Feature)) {
return false;
}
Feature other = (Feature) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
#Override
public String toString() {
return "entity.Feature[ id=" + id + " ]";
}
public String getVariableName() {
return variableName;
}
public void setVariableName(String variableName) {
this.variableName = variableName;
}
public String getVariableData() {
return variableData;
}
public void setVariableData(String variableData) {
this.variableData = variableData;
}
public Patient getPatient() {
return patient;
}
public void setPatient(Patient patient) {
this.patient = patient;
}
}
For single feature counts you can use this
select count(f.patient) from Feature f where f.variableName=:name and f.variableData:=data
Two feature counts
select count(distinct p) from Patient p, Feature f1, Feature f2
where
p.id=f1.patient.id and p.id=f2.patient.id and
f1.variableName=:name1 and f1.variableData:=data1 and
f2.variableName=:name2 and f2.variableData:=data2
Multiple feature counts solution is a bit tricky. org.springframework.data.jpa.domain.Specification can be used
public class PatientSpecifications {
public static Specification<Patient> hasVariable(String name, String data) {
return (root, query, builder) -> {
Subquery<Fearure> subquery = query.subquery(Fearure.class);
Root<Fearure> feature = subquery.from(Fearure.class);
Predicate predicate1 = builder.equal(feature.get("patient").get("id"), root.get("id"));
Predicate predicate2 = builder.equal(feature.get("variableName"), name);
Predicate predicate3 = builder.equal(feature.get("variableData"), data);
subquery.select(operation).where(predicate1, predicate2, predicate3);
return builder.exists(subquery);
}
}
}
Then your PatientRepository have to extend org.springframework.data.jpa.repository.JpaSpecificationExecutor<Patient>
#Repository
public interface PatientRepository
extends JpaRepository<Patient, Long>, JpaSpecificationExecutor<Patient> {
}
Your service method:
#Service
public class PatientService {
#Autowired
PatientRepository patientRepository;
//The larger map is, the more subqueries query would involve. Try to avoid large map
public long countPatiens(Map<String, String> nameDataMap) {
Specification<Patient> spec = null;
for(Map.Entry<String, String> entry : nameDataMap.entrySet()) {
Specification<Patient> tempSpec = PatientSpecifications.hasVariable(entry.getKey(), entry.getValue());
if(spec != null)
spec = Specifications.where(spec).and(tempSpec);
else spec = tempSpec;
}
Objects.requireNonNull(spec);
return patientRepository.count(spec);
}
}
We also handled same situation for two feature and after extracting the IDs, we used a nested loops after and counting the number of common count. It was resource intensive and this two feature query in the answer helped a lot.
May need to redesign the Class Structure so that querying is easier.

Linking two object by code (not ID) using Eclipselink JPA

i have two tables:
area (
id int PK autoincrement
code varchar
)
products (
id int PK autoincrement
area_id int
)
And the objets are defined like this:
class Product {
...
#JoinColumn(name = "area_id", referencedColumnName = "id")
#ManyToOne
#Expose
private Area area;
...
}
This works fine but I want that area to be a String with the code used in the table area column code.
class Product {
...
???
private String area;
...
}
What should be the annotations to make this work?
Thanks!
Try to use a combination of #SecondaryTable and #Column annotations. Something like this:
#Entity
#SecondaryTable(name="area", pkJoinColumns=#PrimaryKeyJoinColumn(name="id", referencedColumnName="area_id"))
class Product {
...
#Column(name="code", table = "area")
private String code;
...
}
If there is some poor soul with the same problem, here is how I did it:
Using transformers. So the field area is defined like this:
#Transformation(fetch = FetchType.EAGER, optional = false)
#ReadTransformer(transformerClass = AreaAttributeTransformer.class)
#WriteTransformers({
#WriteTransformer(
transformerClass = AreaFieldTransformer.class,
column = #Column(name = "area_id", nullable = false))
})
#Expose
private String area;
Then those clases work like this:
AreaAttributeTransformer
public class AreaAttributeTransformer implements AttributeTransformer {
private AbstractTransformationMapping mapping;
#Override
public void initialize(AbstractTransformationMapping abstractTransformationMapping) {
this.mapping = abstractTransformationMapping;
}
#Override
public Object buildAttributeValue(Record record, Object o, Session session) {
for (DatabaseField field : mapping.getFields()) {
if (field.getName().contains("area_id")) {
EntityManager em = MyEntityManagerFactory.getENTITY_MANAGER_FACTORY().createEntityManager();
List results = em.createNamedQuery("Areas.findById")
.setParameter("id", record.get(field))
.getResultList();
if (results.size() > 0)
return ((Area) results.get(0)).getCode();
}
}
return null;
}
}
AreaFieldTransformer
public class AreaFieldTransformer implements FieldTransformer {
private AbstractTransformationMapping mapping;
#Override
public void initialize(AbstractTransformationMapping abstractTransformationMapping) {
this.mapping = abstractTransformationMapping;
}
#Override
public Object buildFieldValue(Object o, String s, Session session) {
if (o instanceof RouSub) {
EntityManager em = MyEntityManagerFactory.getENTITY_MANAGER_FACTORY().createEntityManager();
List results = em.createNamedQuery("Area.findByCode")
.setParameter("area", ((Area) o).getCode())
.getResultList();
if (results.size() > 0)
return ((Area)results.get(0)).getId();
}
return null;
}
}

Creating JPA entity with composite primary key with #Id from #MappedSuperclass

I have a class hierarchy for JPA entities with the base class being a MappedSuperclass which has one ID defined. I am trying to use a composite key in a subclass however that does not seem to work
My code looks like this
#MappedSuperclass
public class BaseEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
protected Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
#Entity
#EntityListeners(EntityBaseListener.class)
#Inheritance(strategy=InheritanceType.JOINED)
#Table(name = "catalog_entity")
public class BaseCatalogEntity extends BaseEntity {
#Column(name = "created_at", nullable = false)
#Temporal(TemporalType.TIMESTAMP)
private Date createdAt;
#Column(name = "updated_at", nullable = false)
#Temporal(TemporalType.TIMESTAMP)
private Date updatedAt;
public void setCreatedAt(Date date)
{
createdAt = date;
}
public void setUpdatedAt(Date date)
{
updatedAt = date;
}
public Date getCreatedAt() {
return createdAt;
}
public Date getUpdatedAt() {
return updatedAt;
}
}
#Entity
#Table(schema = "student_catalog")
#IdClass(value = StudentCatalog.StudentCatalogPK.class)
public class StudentCatalog extends BaseCatalogEntity {
#Id
#Column(name = "name", nullable = false, length = 100)
private String name;
#Id
#Column(name = "version", nullable = false)
private Integer version;
#Column(name = "description" , length = 255)
private String description;
#Column(name = "vendor" , length = 50)
private String vendor;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getVendor() {
return vendor;
}
public void setVendor(String vendor) {
this.vendor = vendor;
}
public static class StudentCatalogPK implements Serializable {
private Long id;
private String name;
private Integer version;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
#Override
public boolean equals(Object obj) {
boolean result = false;
if(obj != null && (obj instanceof StudentCatalogPK)) {
StudentCatalogPK other = (StudentCatalogPK)obj;
result = (Objects.equals(this.id, other.id) && Objects.equals(this.name, other.name) &&
Objects.equals(this.version, other.version));
}
return result;
}
#Override
public int hashCode() {
return (27780 + (this.id != null ? this.id.hashCode() : 0) +
(this.version != null ? this.version.hashCode() : 0) +
(this.name != null ? this.name.hashCode() : 0));
}
}
}
I get the following exception:
Exception Description: Invalid composite primary key specification. The names of the primary key fields or properties in the primary key class [com.example.jpa.StudentCatalog$StudentCatalogPK] and those of the entity bean class [class com.example.jpa.StudentCatalog] must correspond and their types must be the same. Also, ensure that you have specified ID elements for the corresponding attributes in XML and/or an #Id on the corresponding fields or properties of the entity class.
I am using Eclipselink 2.5.1. Is there a way I can get this to work without changing the BaseEntity and BaseCatalogEntity classes?
It is not legal in JPA to redefine the id in subclasses. This would lead to ambiguities in the table mappings as well as in polymorphic queries.
The desire to extend the key defined in a superclass is a common issue when business keys are used for DB identity. I would advise to use only surrogate keys (like UUID) for DB identity and business keys for instance identity.
Under following conditions:
your base entity should use TABLE_PER_CLASS inheritance (and as I can see it is)
your base entity (composite key) key is of the same type as that one you want to have in your derived class (so there should be also composite key of String and Integer).
You can use #AttributeOverride annotation under class declaration, removing #Id fields from it:
#AttributeOverride(name = "id", column = #Column(name = "NAME"))
This - in result, can change column name in derived entity's table and that's the most you can acheive.
When using #MappedSuperClass, it would be advisable to make the BaseEntity Class as abstract and then extending the Base class from other Entity classes.
Cleaner approach keeping inheritance in mind and designing your application.