How can I add methods to an entity (Extending) - java

Suppose I have an entity like this
package entity;
[imports]
#Entity
#Table(name = "category")
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#NotNull
#Column(name = "id")
private Short id;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 45)
#Column(name= "date")
#Temporal(TemporalType.TIMESTAMP)
private Date date;
public Category() {
}
public Category(Short id) {
this.id = id;
}
public Category(Short id, Date date) {
this.id = id;
this.date= date;
}
public Short getId() {
return id;
}
public void setId(Short id) {
this.id = id;
}
public String getDate() {
return date;
}
public void setDate(Date date) {
this.date= date;
}
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
}
I need to add some methods to this entity. eg the above example has Date but what I need is Calendar. So I tried extending the Category by
public class CategoryExt extends Category{
private Calendar calendar;
public Calendar getCalendar() {
Calendar calendar = new Calendar();
calendar.setTime(this.getDate());
return calendar;
}
}
But when I try to cast or specify that the fetched items should be of collection
Collection<CategoryExt> I get an error that Category cannot be converted to CategoryExt which I am guessing has something to do with downcasting error.
How do I properly add methods by extending? I don't want to alter the entity itself because I don't want to keep on modifying it every time I autogenerate it with the IDE.
Thank you in advance.

In this case you shouldn't extend Category entity at all.
Just create some helper class with function which returns Calendar if you pass Date object.

Switch the Inheritance:
public class Category extends CategoryBase implements Serializable {...}
With a abstract class
public abstract class CategoryBase {
public abstract long getDate(); // long or whatever
public Calendar getCalendar() {
Calendar calendar = new Calendar();
calendar.setTime(getDate());
return calendar;
}
}
But know: To extend a Javabean for a non-Javabean-method is ugly.

See, if you get an entity i.e.:
session.get(Category.class,1);
The result is not a Category, its already a superclass of Category (or proxy, sometimes adapter).
What you try to do is very ugly because Category is a JavaBean, a java-bean does not support such business-logic like extra-methods or complex calculations.

Take a look at following tutorial, it will help you to build your inheritance. Basicly you need to introduce discriminator property in table and JPA will then know which entity is persisted in this row. On the other hand you cannot "cast" one entity to another. This will not work.
http://docs.oracle.com/javaee/6/tutorial/doc/bnbqn.html

Related

Hibernate MappingException: Could not determine type of custom object type using id class

So, I'm trying to persist an entity in the database that has a composite key, declared using the #IdClass annotation, which one of the ID keys I have turned into an object so ensure some validation of the data.
Before, when this ID was just a String, it was working without any problems, but now that I have changed it's type, it seens that Hibernate can't determine it's type in the database.
I found a question with a problem that was almost exactly the same as the mine, here. After I added the #Column annotation to the fields in the IdClass, I feel that the Hibernate could determine the type of the field in the database, but now it fails to perform the conversion.
I already have the converter class with the #Converter annotation and implementing the AttributeConverter interface, but I think that it isn't being reached by the Spring/Hibernate.
The involved classes bellow:
The converter
#Converter
public class ChapterNumberConverter implements AttributeConverter<ChapterNumber, String> {
#Override
public String convertToDatabaseColumn(ChapterNumber attribute) {
String value = attribute.getValue();
return value;
}
#Override
public ChapterNumber convertToEntityAttribute(String dbData) {
ChapterNumber chapterNumber = new ChapterNumber(dbData);
return chapterNumber;
}
}
The composite ID class
public class ChapterID implements Serializable {
private static final long serialVersionUID = 4324952545057872260L;
#Column
private Long id;
#Column
#Convert(converter = ChapterNumberConverter.class)
private String number;
#Column
private Long publisher;
#Column
private Long manga;
public ChapterID() {
}
public ChapterID(Long id, String number, Long publisher, Long manga) {
this.id = id;
this.number = number;
this.publisher = publisher;
this.manga = manga;
}
// ... getters and setters
}
The entity class
#Entity
#Table(name = "chapter", uniqueConstraints = #UniqueConstraint(columnNames = {"number", "publisher_id", "manga_id"}))
#IdClass(ChapterID.class)
public class Chapter {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#Id
#Convert(converter = ChapterNumberConverter.class)
private ChapterNumber number;
#Id
#ManyToOne
#JoinColumn(name = "publisher_id")
private Publisher publisher;
#Id
#ManyToOne
#JoinColumn(name = "manga_id")
private Manga manga;
#Column(nullable = false)
#Convert(converter = ChapterLanguageEnumConverter.class)
private ChapterLanguage language;
public Chapter() {
}
public Chapter(ChapterNumber chapterNumber, Publisher publisher, Manga manga, ChapterLanguage language) {
this.number = chapterNumber;
this.publisher = publisher;
this.manga = manga;
this.language = language;
}
public Chapter(String chapterNumber, Publisher publisher, Manga manga, ChapterLanguage language) {
this(new ChapterNumber(chapterNumber), publisher, manga, language);
}
// ... getters and setters
}
I just want to validate the number field in the entity class, so, if there is another way to do this without using a custom type, otherwise, if anyone knows what I can do to teach correctly the Hibernate how to persist this field, tell me please 😢

Enum type fields in JPA Entity

Is it possible to use Enums as a type of a field (column) in custom JPA entities? Here is an example:
#Getter #Setter
#Entity
#Table(name = "payments")
public class PaymentEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "status")
private Integer statusId;
public PaymentStatuses getStatus() {
return PaymentStatuses.valueOf(statusId);
}
public PaymentEntity setStatus(PaymentStatuses status) {
statusId = status == null ? null : status.getId();
return this;
}
}
public enum PaymentStatuses {
CREATED(1),
COMPLETED(2),
CANCELED(3);
private Integer id;
private PaymentStatuses(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public static PaymentStatuses valueOf(Integer id) {
for (PaymentStatuses value : values())
if (value.getId().equals(id))
return value;
return null;
}
}
Code above works fine, but approach with statusId and getStatus setStatus looks ugly a little bit.
I wanna use PaymentStatuses as a type of the field in my entity. Like this:
#Getter #Setter
#Entity
#Table(name = "payments")
public class PaymentEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "status")
private PaymentStatuses status;
}
Tell me please, is it possible?
Using #Enumerated(EnumType.ORDINAL) will not work because the ORDINAL mode starts to 0.
So the 3 first values of the enum will be represented in DB with the 0, 1 and 2 values.
while the id field in the enum to represent it in the DB goes from 1 to 3 :
CREATED(1),
COMPLETED(2),
CANCELED(3);
Besides, this way would correlate the order of elements defined in the enum with the way to represent them in database. Which not a good thing as enum values could be added/removed in the future.
A better way to address your issue is defining a javax.persistence.AttributeConverter and using it with #Convert.
So create a AttributeConverter implementation to indicate how to convert from the DB to the enum and the enum to the DB.
Then declare a PaymentStatuses status in your entity and annotate it with #Convert by specifying the AttributeConverter implementation class.
#Getter #Setter
#Entity
#Table(name = "payments")
public class PaymentEntity {
...
#Convert(converter = PaymentStatusesConverter.class)
private PaymentStatuses status;
...
}
public class PaymentStatusesConverter implements AttributeConverter<PaymentStatuses, Integer> {
#Override
public Integer convertToDatabaseColumn(PaymentStatuses status) {
return status.getId();
}
#Override
public PaymentStatuses convertToEntityAttribute(Integer status) {
return PaymentStatuses.valueOf(status);
}
}
Yes, but when you save to the database it will persist the current index of the enum value (in your case 0 for CREATED, 1 for COMPLETED, etc.) which will give you trouble if you change the enum values. To avoid this you can use the #Enumerated annotation like:
#Getter #Setter
#Entity
#Table(name = "payments")
public class PaymentEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "status")
#Enumerated(EnumType.ORDINAL) // There is also EnumType.STRING, or you can define a custom EnumType
private PaymentStatuses status;
}
You can use #Enumerated(EnumType.STRING) if you want your data to be stored in the db like the name of the Java enum name (CREATED, COMPLETED, CANCELED)

Why #Column might not work for methods?

I have a Spring Boot application with JPA and Hibernate Maven dependencies. Database is PosgreSQL.
I would like to create fields in a database based on methods.
So I have an entity class:
#Entity
#Table(name="test_my_entity")
public class MyEntity {
#Id
#GeneratedValue
private Long id;
protected String name;
#Transient
#XmlSchemaType(name = "dateTime")
protected XMLGregorianCalendar myXMLDate;
protected Calendar myDateProperty;
#Column(name = "my_date")
private Calendar isCalendar() {
return new GregorianCalendar(myXMLDate.getYear(), myXMLDate.getMonth(), myXMLDate.getDay());
}
#Column(name = "my_str")
public String myStr() {
return "My string";
}
public MyEntity() {
}
}
However I receive the following structure:
All annotations on methods are ignored.
Could anyone please give me some advice why it might happen and how to create needed fields properly?
The methods must follow, Java Bean convention; precisely, public Getters and Setters. Moveover, properties must exist. Try this,
#Entity
#Table(name="test_my_entity")
public class MyEntity {
private Long id;
protected String name;
private Calendar myDate;
private String myStr;
#Transient
#XmlSchemaType(name = "dateTime")
protected XMLGregorianCalendar myXMLDate;
#Id
#GeneratedValue
public Long getId() {
return id;
}
#Column(name = "my_date")
public Calendar getMyDate() {
return myDate;
}
#Column(name = "my_str")
public String getMyStr() {
return myStr;
}
// I don't get its purpose; hence not touching it.
private Calendar isCalendar() {
return new GregorianCalendar(myXMLDate.getYear(), myXMLDate.getMonth(), myXMLDate.getDay());
}
}
Refs:
List of types Hibernate understand by default.
How to make Hibernate to understand your type.
So I have found the solution. So that you can receive values from methods you should add #Access annotations and also make #Transient variables.
So Hibernate will create neccesary fields and during commit will use values from methods to fulfill them.
Also here is an example of how to convert XMLGregorianCalendar to Calendar -
the format that Hibernate can use successfully.
Here is a working example:
#Entity
#Access(AccessType.FIELD)
#Table(name="test_my_entity")
public class MyEntity {
#Id
#GeneratedValue
private Long id;
#XmlAttribute(name = "name")
#Transient
protected String name;
#XmlSchemaType(name = "dateTime")
#Transient
protected XMLGregorianCalendar myXMLDate;
#Transient
private Calendar calendarDate;
#Access(AccessType.PROPERTY)
#Column(name = "calendar_date")
private Calendar getCalendarDate() {
return new GregorianCalendar(myXMLDate.getYear(), myXMLDate.getMonth(), myXMLDate.getDay());
}
#Access(AccessType.PROPERTY)
#Column(name = "my_str_name")
public String getName() {
return "My string";
}
//...setters here
public MyEntity() {
}
}

#Formula not working in hibernate with object

I have a enum of few status value
NEW, REVIEWD, PUBLISHED, PENDING, UPDATED, SPAM, DUPLICATE, IRRELEVANT, UNPUBLISHED
I don't want to use them as enumerated so created one entity for that. For convenient I want to keep a column in entity to initialize status from enum and convert that enumerated value to a Object of status entity. for this..
I have two entity. I want to refer a column with value from another entity.
Basically I want to initialize a object with formula.
Entities are
#Entity
#Table(name = "event_status")
public class EventStatus {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="eventStatusId")
private Integer eventStatusId;
#Enumerated(EnumType.STRING)
#Column(unique = true,name="eventStatusType")
private EventStatusType eventStatusType;
public EventStatus() {
this(EventStatusType.NEW);
}
public EventStatus(EventStatusType eventStatusType) {
super();
this.eventStatusType = eventStatusType;
}
public Integer getEventStatusId() {
return eventStatusId;
}
public EventStatusType getEventStatusType() {
return eventStatusType;
}
public void setEventStatusId(Integer eventStatusId) {
this.eventStatusId = eventStatusId;
}
public void setEventStatusType(EventStatusType eventStatusType) {
this.eventStatusType = eventStatusType;
}
}
I have another entity in which I am referring object of this entity
#Entity
#Table(name = "event_")
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class Event implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "id_")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Transient
public EventStatusType eventStatusType = EventStatusType.NEW;
#ManyToOne(fetch = FetchType.EAGER, targetEntity = EventStatus.class)
#Formula("select * from event_status where eventStatusId= 1")
private EventStatus status;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public EventStatus getStatus() {
System.out.println("Event.getStatus() " + status);
return status;
}
public void setStatus(EventStatus status) {
System.out.println("Event.setStatus()");
this.status = status;
}
}
This is not giving any exception but not initializing this value.
Is it possible to initialize this EntityStatus with value of eventStatusType in Event entity
I would like to explain that based on the documentation:
5.1.4.1.5. Formula
Sometimes, you want the Database to do some computation for you rather than in the JVM, you might also create some kind of virtual column. You can use a SQL fragment (aka formula) instead of mapping a property into a column. This kind of property is read only (its value is calculated by your formula fragment).
#Formula("obj_length * obj_height * obj_width")
public long getObjectVolume()
The SQL fragment can be as complex as you want and even include subselects.
...
5.1.7.1. Using a foreign key or an association table
...
Note
You can use a SQL fragment to simulate a physical join column using the #JoinColumnOrFormula / #JoinColumnOrformulas annotations (just like you can use a SQL fragment to simulate a property column via the #Formula annotation).
#Entity
public class Ticket implements Serializable {
#ManyToOne
#JoinColumnOrFormula(formula="(firstname + ' ' + lastname)")
public Person getOwner() {
return person;
}
...
}
Also, we should use insertable = false, updatable = false, because such mapping is not editable

Spring Data Repository Save Not Returning Instance With Updated Audit Fields

Why does repository.save(myEntity) not return an updated entity with the updated audit fields?
The resulting instance from MyEntityRepository.save(myEntity) and subsequently, from MyEntityService.save(myEntity) does not have the updated updatedOn date. I have verified this is correctly set in the database, so I know that auditing is working. The returned instance's updatedOn date is correct for an insert, but not for an update. I prefer to not have to immediately do a findById after every save, especially if the intent is that save() returns the udpated, attached instance.
Assuming the setting of updatedOn is occurring through a #PreUpdate hook and this hook is triggered during the entityManager.merge() call via repository.save(), I don't follow why the value would not be set on the returned instance.
Example code:
#Entity
#DynamicUpdate
#DynamicInsert
#Table(name = "my_entity", schema = "public")
#SequenceGenerator(name = "pk_sequence", sequenceName = "my_entity_seq", allocationSize = 1)
#AttributeOverrides({#AttributeOverride(name = "id", column = #Column(name = "id", columnDefinition = "int"))})
#EntityListeners(AuditingEntityListener.class)
public class MyEntity {
protected Integer id;
#LastModifiedDate
private Date updatedOn;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "pk_sequence")
#Column(name = "id", nullable = false, columnDefinition = "bigint")
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
#Version
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "updated_on")
public Date getUpdatedOn() {
return updatedOn;
}
public void setUpdatedOn(Date updatedOn) {
this.updatedOn = updatedOn;
}
}
public interface MyEntityRepository extends JpaRepository<MyEntity, Integer> { }
#Service
#Transactional(readOnly = true)
public class MyEntityService {
#Autowired
private MyEntityRepository repository;
#Transactional
public MyEntity save(MyEntity myEntity) {
return repository.save(myEntity);
}
}
I faced with the same issue.
In my case the important items that helped me to solve this problem were:
1) use repository.saveAndFlush(...) method
2) use findAllById() or findByYourOwnQuery() (annotated with #Query).
Overall, my test case looked like this:
UserAccount userAccount = UserAccount.builder().username(username).build();
userAccountRepository.saveAndFlush(userAccount);
final LocalDateTime previousUpdateDate = userAccount.getUpdateDate();
....
List<BigInteger> ids = Arrays.asList(userAccountId);
UserAccount updatedUserAccount = userAccountRepository.findAllById(ids).get(0); // contains updated Audit data fields
...
assertThat(actual.getUpdateDate(), is(greaterThan(previousUpdateDate))); // true
The important thing that you shouldn't use repository.findOne(...) because it caches the reference to the object - read more.
I ran in to the exact same problem. I fixed it by using,
repository.saveAndFlush(myEntity);
instead of
repository.save(myEntity);

Categories

Resources