Why #Column might not work for methods? - java

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() {
}
}

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 😢

Ebean and Play! not filtering columns with .select()

I'm trying to fetch just a part of the model using Ebean in Play! Framework, but I'm having some problems and I didn't found any solutions.
I have these models:
User:
#Entity
#Table(name = "users")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class User extends Model{
#Id
private int id;
#NotNull
#Column(name = "first_name", nullable = false)
private String firstName;
#Column(name = "last_name")
private String lastName;
#NotNull
#Column(nullable = false)
private String username;
#NotNull
#Column(nullable = false)
private String email;
private String gender;
private String locale;
private Date birthday;
private String bio;
#NotNull
#Column(nullable = false)
private boolean active;
private String avatar;
#Column(name = "created_at",nullable = false)
private Date createdAt;
#OneToMany
private List<UserToken> userTokens;
// Getters and Setters omitted for brevity
}
UserToken:
#Entity
#Table(name = "user_tokens")
public class UserToken extends Model {
#Id
private int id;
#Column(name = "user_id")
private int userId;
private String token;
#Column(name = "created_at")
#CreatedTimestamp
private Date createdAt;
#ManyToOne
private User user;
// Getters and Setters omitted for brevity
}
And then, I have a controller UserController:
public class UserController extends Controller{
public static Result list(){
User user = Ebean.find(User.class).select("firstName").where().idEq(1).findUnique();
return Results.ok(Json.toJson(user));
}
}
I expected that, when using the .select(), it would filter the fields and load a partial object, but it loads it entirely.
In the logs, there is more problems that I don't know why its happening.
It is making 3 queries. First is the one that I want. And then it makes one to fetch the whole Model, and another one to find the UserTokens. I don't know why it is doing these last two queries and I wanted just the first one to be executed.
Solution Edit
After already accepted the fact that I would have to build the Json as suggested by #biesior , I found (out of nowhere) the solution!
public static Result list() throws JsonProcessingException {
User user = Ebean.find(User.class).select("firstName").where().idEq(1).findUnique();
JsonContext jc = Ebean.createJsonContext();
return Results.ok(jc.toJsonString(user));
}
I render only the wanted fields selected in .select() after using JsonContext.
That's simple, when you using select("...") it always gets just id field (cannot be avoided - it's required for mapping) + desired fields, but if later you are trying to access the field that wasn't available in first select("...") - Ebean repeats the query and maps whole object.
In other words, you are accessing somewhere the field that wasn't available in first query, analyze your controller and/or templates, find all fields and add it to your select (even if i.e. they're commented with common HTML comment in the view!)
In the last version of Play Framework (2.6) the proper way to do this is:
public Result list() {
JsonContext json = ebeanServer.json();
List<MyClass> orders= ebeanServer.find(MyClass.class).select("id,property1,property2").findList();
return ok(json.toJson(orders));
}

How can I add methods to an entity (Extending)

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

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);

How do I setup annotations for JOINED inheritance with composite PK in hibernate?

I am new to hibernate and having a tough time trying to wrap my head around setting up Joined inheritance with composite Primary Key. With my current setup, I get a:
JDBCException: could not insert: LandHolidayPackage
I am essentially looking for two things:
Are the inheritance annotations in place ?
Is the composite PK setup properly ?
DB Design:
Reference
Here are my classes and the annotations involved:
#Entity
#Table(name = "HOLIDAYPACKAGE")
public final class HolidayPackage {
private Integer idPackage;
private String name;
private Set<HolidayPackageVariant> holidayPackageVariants = new HashSet<HolidayPackageVariant>(0);
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "IDHOLIDAYPACKAGE", nullable = false)
public Integer getIdPackage() {
return idPackage;
}
#OneToMany(fetch=FetchType.LAZY, cascade={CascadeType.ALL}, mappedBy = "holidayPackage")
public Set<HolidayPackageVariant> getHolidayPackageVariants() {
return holidayPackageVariants;
}
// ommitted other part of the code
}
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
#Table(name="HOLIDAYPACKAGEVARIANT")
public abstract class HolidayPackageVariant {
private Integer idHolidayPackageVariant;
private HolidayPackage holidayPackage;
private String typeHolidayPackage;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="IDHOLIDAYPACKAGEVARIANT", nullable=false)
public Integer getIdHolidayPackageVariant() {
return idHolidayPackageVariant;
}
#ManyToOne(fetch=FetchType.LAZY, cascade={CascadeType.ALL})
#JoinColumn(name="IDHOLIDAYPACKAGE", nullable=false)
public HolidayPackage getHolidayPackage() {
return holidayPackage;
}
#Column(name="TYPEHOLIDAYPACKAGE", nullable=true)
public String getTypeHolidayPackage() {
return typeHolidayPackage;
}
// ommitted setters, equals hashCode
}
#Entity
#Table(name="LANDHOLIDAYPACKAGEVARIANT")
public final class LandHolidayPackageVariant extends HolidayPackageVariant{
private static final String LAND = "LAND";
protected LandHolidayPackageVariant() {}
public LandHolidayPackageVariant(HolidayPackage holidayPackage) {
super(holidayPackage, LAND);
}
}
#Entity
#Table(name="FLIGHTHOLIDAYPACKAGEVARIANT")
public final class FlightHolidayPackageVariant extends HolidayPackageVariant{
private static final String FLIGHT = "FLIGHT";
private Destination originCity;
protected FlightHolidayPackageVariant(){}
public FlightHolidayPackageVariant(HolidayPackage holidayPackage,
Destination originCity) {
super(holidayPackage, FLIGHT);
setOriginCity(originCity);
}
#ManyToOne(fetch=FetchType.LAZY, cascade={CascadeType.ALL})
#JoinColumn(name="IDDESTINATION", nullable=false)
public Destination getOriginCity() {
return originCity;
}
// ommited other setters etc functions
}
You annotated the properties in stead of the fields. JPA by default tries to access the fields. If you want JPA to use the fields you have to annotate the class with #AccessType(AccessType.Field).

Categories

Resources