How to create meta annotations on field level? - java

I have this hibernate class with annotations:
#Entity
public class SimponsFamily{
#Id
#TableGenerator(name = ENTITY_ID_GENERATOR,
table = ENTITY_ID_GENERATOR_TABLE,
pkColumnName = ENTITY_ID_GENERATOR_TABLE_PK_COLUMN_NAME,
valueColumnName = ENTITY_ID_GENERATOR_TABLE_VALUE_COLUMN_NAME)
#GeneratedValue(strategy = GenerationType.TABLE, generator = ENTITY_ID_GENERATOR)
private long id;
...
}
Since I don´t won´t to annotate every id field of my classes that way, I tried to create a custom anotation:
#TableGenerator(name = ENTITY_ID_GENERATOR,
table = ENTITY_ID_GENERATOR_TABLE,
pkColumnName = ENTITY_ID_GENERATOR_TABLE_PK_COLUMN_NAME,
valueColumnName = ENTITY_ID_GENERATOR_TABLE_VALUE_COLUMN_NAME)
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface EntityId {
#GeneratedValue(strategy = GenerationType.TABLE, generator = ENTITY_ID_GENERATOR)
public int generator() default 0;
#Id
public long id() default 0;
}
so that I can use this annotation in my class:
#Entity
public class SimponsFamily{
#EntityId
private long id;
...
}
I do have to write the #Id and the #GeneratedValue annotions on field level since they do not support the TYPE RetentionPolicy. This solutions seems to work.
My questions:
How are the field level annotations in my custom annotations(and values) transferred to my usage of EntityId annotation?
What about the default values which I set in my custom annotation, are they used since I do not specify attributes at the usage?
It is a preferred way to use annotations on field level in annotations?

I think I can aswer your third question.
One common way to do what you want (avoid duplicating ID mapping) is to create a common superclass that holds the annotated id and version (for optimistic locking) fields, and then have all persistent objects extend this superclass.
To ensure the superclass is not considered an Entity on its own, it must be annotated with #MappedSuperclass.
Here is a sample (sorry for typos, I don't have an IDE at hand right now) :
#MappedSuperclass
public class PersistentObject {
#Id // Put all your ID mapping here
private Long id;
#Version
private Long version;
}
#Entity
public class SimpsonsFamily extends PersistentObject {
// Other SimpsonFamily-specific fields here, with their mappings
}

Related

How do I create a safe Lombok JPA entity?

I have an #Entity with 20 fields including the index and a timestamp updated by Hibernate:
#Entity
public class MyEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#UpdateTimestamp
private LocalDateTime updatedTime;
private String ....
private String ....
I have a default constructor for Hibernate and a secondary constructor to set everything but the id and updatedTime.
I don't need (or want) setters for id or updatedTime because I only want Hibernate to set them, and it does that with reflection.
I wanted to try out Lombok to see if I could avoid a lot of boilerplate involved here but #Data adds both getters and setters and doesn't create the same constructors.
I'm also concerned that Lomboks generated equals/hashCode and toString methods can cause subtle problems with Hibernate.
This will mean I will have to use a combination of the other Lombok annotations to do this.
How do I safely create an Entity using Lombok like this?
Am I going to have to use a mixture of annotations and manual methods?
Some lombok annotations like #EqualsAndHashCode and #ToString have Exclude option. But neither #Data nor #AllArgsConstructor has a similar option.
But #Data generates setters for all fields for which a setter is not already defined. So you would define a setter as below for the required fields, which does nothing.
private void setId(Long id) {
// Do nothing
}
Instead of the #AllArgsConstructor, you could either use #RequiredArgsConstructor, but annotate all the fields to be in the constructor with #NonNull (or the field should be final).
Refer this answer for RequiredArgsConstructor.
My suggested approach : Another way would be to use #Builder annotation along with #AllArgsConstructor(access = AccessLevel.PRIVATE). (NOTE : Builder by default adds a private all argument constructor, but this is done only if there are no other constructors. But in your case, a default constructor exists and you need to explicitly mention the all args annotation.)
This would prevent the use of the constructor from outside, but at the same time allow you to create objects using the builder. At this point, you could set the values to id and updateTime using the builder. To prevent this you need to add the below code as well.
public static class MyEntityBuilder {
// access is restricted using
// these private dummy methods.
private MyEntityBuilder id(Long id) {
return this;
}
private MyEntityBuilder updateTime(LocalDateTime time) {
return this;
}
}
So, even though it is not possible to achieve your requirement directly, you could do so by adding two dummy setter methods and another two dummy methods within the builder class.
we have #NoArgsConstructor #AllArgsConstructor for generating constructor with lombok.
this is how i create them.
#Entity
#Table(schema = "S25", name = "bank")
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Getter
#Setter
public class Bank {
#Id
#SequenceGenerator(name = "bankEntitySeq", sequenceName = "SEQ_BANKS", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "bankSeq")
#Column(name = "bank_id")
private Long bankId;
#Column(name = "bank_name")
private String bankName;
#Column(name = "created_on")
private Date createdOn =
new Date(); //Date.from(Instant.now().atZone(ZoneId.of("UTC")).toInstant());
}

idMoneda property not updated in DB with #Transient directive

I have this entity, in which I have made a PUT and POST method, which do not give an error but nevertheless the idMoneda, which is a property calculated with #Transient because it is the ID of the moneda(where there is a 1 to 1 relationship with another table), it does not update me, when I look at the database it remains null even though in the POST request I put a value. I don't know if it's because the setter is wrong, or just that something else needs to be added that I don't see right now.
#Entity
#Table(name = "REMESA")
public class Remesa {
#Id
#SequenceGenerator(name = "remesa_sequence", sequenceName = "remesa_sequence", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "remesa_sequence")
#Column(name = "ID")
private Long id;
#OneToOne
#JoinColumn(name = "moneda", nullable = true)
#JsonIgnore
private Moneda moneda;
#Transient
#JsonProperty("moneda")
private Long idMoneda;
public Long getIdMoneda() {
return this.moneda.getId();
}
public void setIdMoneda(Long idMoneda) {
this.idMoneda = idMoneda;
}
}
#Transient in JPA means: do not save this field in DB. A column named "moneda_id" will automatically be generated by your relationship if it's well-defined
Java's transient keyword is used to denote that a field is not to be serialized, whereas JPA's #Transient annotation is used to indicate that a field is not to be persisted in the database, i.e. their semantics are different. Because they have different meanings.
So try to remove the transient annotation and run your code .

How to provide Initial value OR Increment ID with JPA GenerationType.AUTO

I am using following code to define MyEntity,
#Entity
#Table(name = "MY_TABLE")
public class MyEntity {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "MY_TABLE_ID")
private Integer myTableId;
#Column(name = "MY_TABLE_NM")
private String myTableName;
//Getters Setters
}
For the first POST after my application starts, I create MyEntity everything works fine, MY_TABLE_ID starts with 1 and works as expected.
My issue is, If somebody inserts data manually before I do my POST then I get duplicate key exception as myTableId is entered as 1 which is already present.
My main problem is I can't create database sequence for using GenerationType.SEQUENCE now to resolve this as database can't be altered now.
I have tried various combinations of GenerationType, TableGenerator but I am unable to successfully tackle it.
Setting initialValue to some larger number to avoid duplicate values can temporarily resolve my problem but I am unable to do it too.
If someone can help me with initialValue with AUTO or give me some other better solution without database changes will be great :)
As MY_TABLE_ID is an identity column, following annotations will work.
#Entity
#Table(name = "MY_TABLE")
public class MyEntity {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY) // <-- IDENTITY instead of AUTO
#Column(name = "MY_TABLE_ID")
private Integer myTableId;
#Column(name = "MY_TABLE_NM")
private String myTableName;
//Getters Setters
}
The identity column will automatically assign an value as soon as the transaction is committed. You are not to set any values for an identity column, as its the job of the database to assign the values. Therefore you also don't need to think about any initial values (forget them completely for identity columns)
I tried various options in answers provided here and for similar questions on stackoverflow and other forums,
I had few limitations,
I couldn't create database sequence as my database changes were freezed.
I didn't want to introduce new Custom IdGenerator class because it would add confusion to other people working with me.
It was resolved using following change:
Adding GenericGenerator with increment strategy helped me, I made following changes to my code.
#Entity
#Table(name = "MY_TABLE")
public class MyEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator="seq")
#GenericGenerator(name = "seq", strategy="increment")
#Column(name = "MY_TABLE_ID")
private Integer myTableId;
#Column(name = "MY_TABLE_NM")
private String myTableName;
//Getters Setters
}
It helped me because,
From Hiberbate DOCs
increment
An IdentifierGenerator that returns a long, constructed by counting
from the maximum primary key value at startup. Not safe for use in a
cluster!
Since, it was incrementing already existing myTableId even if it was manually inserted, this resolved my issue.
You can also implement your own generator if you need more control.
See this interface IdentifierGenerator.
So you can get the count of records, for example through a #NamedQuery.
Then you can generate an identifier yourself.
public class MyEntityKeyGenerator implements IdentifierGenerator {
#Override
public Serializable generate(SessionImplementor session, Object object) {
// SELECT count(ent) from MyEntity ent;
Long count = (Long) session.getNamedQuery("count-query").uniqueResult();
// calc and return id value
}
}
Entity:
class MyEntity {
#Id
#GenericGenerator(name = "my_generator",
strategy = "org.common.MyEntityKeyGenerator")
#GeneratedValue(generator = "my_generator")
private Long id;...
Just do not forget about the lock.
I use the generation type Identity, which basically means that the db, takes care of Id generation.
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#MappedSuperclass
#EntityListeners(EntityListener.class)
#EqualsAndHashCode(of = {"id", "createdAt"})
public abstract class AbstractEntity<ID extends Serializable> implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private ID id;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "CREATED_AT", updatable = false)
private Date createdAt;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "UPDATED_AT")
private Date updatedAt;
}
You can also use, Sequence generation:
#Entity
#SequenceGenerator(name="seq", initialValue=1, allocationSize=100)
public class EntityWithSequenceId {
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seq")
#Id long id;
}

Is there a configurable alternative to using #Version?

I'm placing an annotated field with #Version on it in all my JPA domain classes, however this just seems like additional boiler plate. Is there a way to get around this perhaps via configuration?
TIA,
Ole
As far as the JPA specification tells us you can't change the #Version annotation via "configuration". You either use #Version in your program code or you don't.
Referring to the official JPA specification (final version, JPA 2.1) in Section 3.4.2 (page 90) we find:
An entity is automatically enabled for optimistic locking if it has a property or field mapped with a Version mapping.
[...]
If only some entities contain version attributes, the persistence provider runtime is required to check those entities for which version attributes have been specified. The consistency of the object graph is not guaranteed, but the absence of version attributes on some of the entities will not stop operations from completing.
However, you can use the concept of inheritance to provide the #Versiononly in one spot via an abstract base class. This class you be written as follows:
#MappedSuperclass
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class AbstractBaseEntity {
public static final long INVALID_OBJECT_ID = -42;
#Version
private int version;
#Id
#SequenceGenerator(name = "sequence-object", sequenceName = "ID_MASTER_SEQ")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequence-object")
#Column(name = "id")
protected Long objectID = INVALID_OBJECT_ID;
public final int getVersion() {
return version;
}
#Override
public long getObjectID() {
return objectID;
}
// ... maybe other methods or fields ...
}
Thus, all your #Entity annotated sub-classes that inherit from AbstractPersistentEntity are provided with both properties: (i) objectIDand (ii) version at once. For instance, class SomeClass can be written as:
#Entity
public class SomeClass extends AbstractBaseEntity /*implements SomeInterface*/ {
// ... specific methods or fields ...
}
For details on the use of #MappedSuperclass see also this answer.
Hope it helps.

Applying annotations to fields inherited from #MappedSuperclass

Has:
#MappedSuperclass
class Superclass {
#Id
#Column(name = "id")
protected long id;
#Column(name="field")
private long field;
}
and
#Entity
class Subclass extends Superclass {
}
How to annotate inherited id with #GeneratedValue and field with #Index within Subclass?
How to annotate inherited id with #GeneratedValue and field with #Index within Subclass?
AFAIK, you can't. What you can do is overriding attributes and associations (i.e. change the column or join column) using the AttributeOverride and AssociationOverride annotations. But you can't do exactly what you're asking.
For the GeneratedValue, consider using XML mapping to override the strategy if you don't want to declare it in the mapped superclass.
For the Index (which is not a standard annotation by the way), did you actually try to declare it at the table level using Hibernate's Table annotation instead (I'm assuming you're using Hibernate)?
#Table(appliesTo="tableName", indexes = { #Index(name="index1", columnNames=
{"column1", "column2"} ) } )
creates the defined indexes on the
columns of table tableName.
References
JPA 1.0 Specification
Section 2.1.9.2 "Mapped Superclasses"
Section 9.1.10 "AttributeOverride Annotation"
Section 9.1.11 "AttributeOverrides Annotation"
Section 9.1.12 "AssociationOverride Annotation"
Section 9.1.13 "AssociationOverrides Annotation"
Hibernate Annotations Reference Guide
2.4. Hibernate Annotation Extensions
Chapter 3. Overriding metadata through XML
As for #GeneratedValue, it is possible to do like this:
#MappedSuperclass
class Superclass {
#Id
#Column(name = "id")
#GeneratedValue(generator = "id_generator")
protected long id;
#Column(name = "field")
private long field;
}
#Entity
#SequenceGenerator(name = "id_generator", sequenceName = "id_seq")
class Subclass extends Superclass {
}
You might be able to do this if you apply the annotations to the accessor methods instead. (I haven't tried this, so I can't guarantee that it'll work.)
#MappedSuperclass
public class Superclass {
#Id
#Column(name = "id")
public long getId() {
return id;
}
.
#Entity
public class Subclass extends Superclass {
#GeneratedValue
public long getId() {
return super.getId();
}
Just in case anyone else searches for this, I used the following code which adds in some overhead, but for processing Field annotations only shouldn't add that much:
private List<Field> getAllFields() {
List<Field> fieldList = new ArrayList<Field>();
// Add all fields from the current class
fieldList.addAll(Arrays.asList(mElement.getClass().getDeclaredFields()));
// Use an index to iterate over mElement's parent types
Class clazz = mElement.getClass();
// Get any fields from the parent class(es)
while (clazz.getSuperclass() != null) {
fieldList.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
// Set it to that parent class
clazz = clazz.getSuperclass();
}
return fieldList;
}
The returned list would contain all fields for all parent and child classes with mElement being the object you are searching for annotations from. Hope this helps.

Categories