how to save enum value to DB with Hibernate? - java

I tried to fill DB tables with random data and through Hibernate.
But my code fill incompatible data into tables (not exactly incompatible it is index of this element declared at enum, for ex: at ApartmentState - FREE is first element it set to appropriate column it index - 0. But I want to put or FREE as enum or as string).
I couldn't figure out why exactly this happen.
Here is code snippet:
private List<Apartment> generateApartments() {
for (int i = 1; i <= 3; i++) {
for (int j = 2; j <= 5; j++) {
Apartment apartment = new Apartment();
// fill apartment
apartment.setRoomName(generateRoomName());
apartment.setPricePerHour(generatePrice());
apartment.setApartmentState(FREE);
apartment.setRating(getDesiredRating(j));
apartment.setSleepPlaces(getDesiredPlaces(i));
apartment.setActive(true);
// save apartment
apartments.add(apartment);
}
}
return apartments;
}
private Apartment.SleepPlaces getDesiredPlaces(int i) {
switch (i) {
case 1:
return ONE_PLACE;
case 2:
return TWO_PLACES;
case 3:
return THREE_PLACES;
}
return ONE_PLACE;
}
private Apartment.StarRating getDesiredRating(int j) {
switch (j) {
case 2:
return TWO_STARS;
case 3:
return THREE_STARS;
case 4:
return FOUR_STARS;
case 5:
return FIVE_STARS;
}
return TWO_STARS;
}
I need to fill at table some enum values, as rating (2, 3, 4) and sleep places (1, 2..).
But this puts some wrong data into table.
Here is content at workbench:
Why it puts only index, not as string or as enum.
How can I recovering this at desired value, in the future.
Here is snippet from Apartment class:
#Entity
#Table(name = "Apartments")
public class Apartment extends AbstractEntity implements Comparable<Apartment> {
private Integer id;
private String roomName;
private Double pricePerHour;
private ApartmentState apartmentState;
private StarRating rating;
private SleepPlaces sleepPlaces;
private Boolean active;
public enum ApartmentState {
FREE, REQUESTED, BOOKED, LIVED, CLEANING, PROCESSING
}
public enum StarRating {
TWO_STARS("2 stars"), THREE_STARS("3 stars"), FOUR_STARS("4 stars"), FIVE_STARS("5 stars");
private String description;
private StarRating(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
public enum SleepPlaces {
ONE_PLACE("1 person"), TWO_PLACES("2 persons"), THREE_PLACES("3 persons"), FOUR_PLACES("4 persons");
private String count;
private SleepPlaces(String count) {
this.count = count;
}
public String getCount() {
return count;
}
}
For me the best is to put enum as enum (possibly at MySql workbench) or as a string (and use name() and valueOf() from Enum class).
But how to implement it with hibernate.
How to solve this trouble?

You can add following enumeration, to indicate you want the String representation to be persisted :
#Enumerated(EnumType.STRING)
private ApartmentState apartmentState;

Use this annotation at field level:
#Enumerated(EnumType.STRING)

I also had to add
#Embeddable to the java enum
#Embeddable
public enum ApartmentState {
FREE, REQUESTED, BOOKED, LIVED, CLEANING, PROCESSING
}

Related

how to get name value associated with integer in dto

i'm using java, for example, i have 2 tables staff(id, name, status_id, company_id) and company(id, name), the corresponding entity looks like:
public class Staff {
private Integer id;
private String name;
private Integer statusId;
private Integer companyId;
private Company company;
}
public class Company {
private Integer id;
private String name;
private List<Staff> staffList;
}
for status_id of table staff, 0 means New, 1 represents Active and 2 stands for Inactive.
I need to show New, Active or Inactive on html page/excel when describe a staff status rather than 0, 1 or 2.
And I have a StaffDto:
public class StaffDto {
private Integer id;
private String name;
private Integer statusId;
private String companyName;
}
my questions are:
the statusName(New/Active/Inactive) should be in StaffDto, such that there is no need to calculate status name according to statusId on each client, right?
what is a good practice to get statusName base on statusId?
I should write code like
public class StaffDto {
private Integer statusId;
private String statusName;
public String getStatusName() {
switch(statusId) {
case 0: return "New";
case 1: return "Active";
case 2: return "Inactive";
}
}
}
is this a good practice? or something else(e.g. enum) is better?
if the logic of getting status name is added in StaffDto, what if there is another dtoj(e.g. ADto) also need to show status name, then I have to re-write this logic in ADto?
what if one client need to show New, Active or Inactive, while another client need to show A, B or C or something else, then what should I return in StaffDto? do I still return New, Active or Inactive in StaffDto, and other client need to calculate N, A or I base on statusId on their client? or should I return something else to client instead of xxxDto?
I too would go for enum as you mentioned, bind the status code to the name
then, you do not have to rewrite the logic in DTOs, Make your model have the enum rather than code or name
enum can have its own methods like getShortName for different representations
enum Status {
NEW(0), Active(1), InActive(2);
private final int code;
Status(int code) {
this.code = code;
}
public String getShortName() {
return this.name().substring(0, 1).toUpperCase();
}
public int getCode() {
return code;
}
}

How to use to converter in sping jpa

To run the application i use tomcat 8.5.50 package in war.
i use spring 5.2 version.
in my code i want to use LocalDataTime like this:
#Entity
#Table(name="meals")
public class Meal {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Integer id;
#Column(name = "date_time")
#Convert(converter = MealConverter.class)
private LocalDateTime datetime;
#Column(name = "description")
private String description;
#Column(name = "calories")
private int calories;
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
public LocalDateTime getDatetime() {
return datetime;
}
public void setDatetime(LocalDateTime datetime) {
this.datetime = datetime;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public int getCalories() {
return calories;
}
public void setCalories(int calories) {
this.calories = calories;
}
}
my Converter:
#Converter(autoApply = true)
public class MealConverter implements AttributeConverter<Meal, String> {
private static final String SEPARATOR = ", ";
#Override
public String convertToDatabaseColumn(Meal meal) {
StringBuilder sb = new StringBuilder();
sb.append(meal.getCalories()).append(SEPARATOR)
.append(meal.getDatetime())
.append(SEPARATOR)
.append(meal.getDescription());
return sb.toString();
}
#Override
public Meal convertToEntityAttribute(String dbData) {
String[] rgb = dbData.split(SEPARATOR);
Meal meal = new Meal(Integer.valueOf(rgb[0]),
LocalDateTime(valueOf(rgb[1]),
rgb[2],
rgb[3]);
return meal;
}
}
I am trying to use the converter in the convertToEntityAttribute method but the compiler does not allow me to do this. What needs to be fixed in my Converter?
Meal meal = new Meal(Integer.valueOf(rgb[0]),
LocalDateTime(valueOf(rgb[1]),
rgb[2],
rgb[3]);
Your Meal class doesn’t seem to have any explicit constructor, so you cannot pass arguments to new Meal(). You seem to be trying to pass two arguments. You may want to create a suitable constructor, or you may want to pass the two values into the Meal using setters after the object has been created.
LocalDateTime is a class, but you seem to try to call it as a method with three arguments. If that’s java.time.LocalDateTime, you probably intended LocalDateTime.of(someArguemts), but there isn’t any three-argument of method of that class. If you explain better what result you expect, we can guide you better.
As the first argument to LocalDateTime you have a call to a valueOf method that doesn’t seem to be declared in your class. You may have intended Integer.valueOf as in the preceding line.
If you are trying to use your RGB values for initializing a date (don’t know what sense that might make), be aware that if your RGB values go up to 255, this will likely fail with an exception since month numbers only go up to 12 and day of month up to 31.
I am far from sure that the following is correct or does what you want it to do, but it’s a guess at what you may be after.
#Override
public Meal convertToEntityAttribute(String dbData) {
String[] fields = dbData.split(SEPARATOR);
Meal meal = new Meal();
meal.setCalories(Integer.parseInt(fields[0]));
meal.setDateTime(LocalDateTime.parse(fields[1]));
meal.setDescription(fields[2]);
return meal;
}
I am trying to do the opposite of your convertToDatabaseColumn method. I have discarded the variable name rgb because I didn’t see how it couldn’t be misleading here.

JPA AttributeConverter search predicate in specification

I am trying to have a class that has a certain list of objects (specified by another class) persisted in the database as a string (use JPA Converter - all good).
And then I want to use Specification to search inside that string.
What is the best way to create the predicates? I don't seem to understand the connection bettween the AttributeConverter and the Expression in the Specification.
The parent class:
#Entity #Table
public class A {
#Column #Id #GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Column
private String name;
#Enumerated(EnumType.STRING)
private SomeType type;
#Column(length=1000) #Convert(converter = BConverter.class)
private List<B> bList;
private Integer no;
}
The listed object class:
public class B{
private String type;
private Integer quantity;
}
The converter:
#Converter
public class BConverter implements AttributeConverter<List<B>, String> {
private static final String SEPARATOR = ":";
private static final String LIST_SEPARATOR = ";";
#Override public String convertToDatabaseColumn(List<B> bList) {
return bList.stream().map(e->convertToString(e)).collect(Collectors.joining(LIST_SEPARATOR));
}
#Override public List<B> convertToEntityAttribute(String str) {
if(str==null || str.isEmpty() ) return null;
return Arrays.stream(str.split(LIST_SEPARATOR)).map(e->convertFromString(e)).collect(Collectors.toList());
}
private String convertToString(B b){
if(entity==null) return null;
return b.getType().toString() +SEPARATOR+ b.getQuantity().toString();
}
private B convertFromString(String subStr){
if(subStr==null || subStr.isEmpty() ) return null;
String[] pair = subStr.split(SEPARATOR);
return new B(pair[0],Integer.valueOf(pair[1]));
}
}
In the database should look something like:
Table A:
id: 1;
name: "Some Name";
type: "THISTYPE";
blist: "TYPE1:11;TYPE2:22";
no: 0;
id: 2;
name: "Other Name";
type: "THISTYPE";
blist: "TYPE1:45;TYPE2:56";
no: 12;
I would then like to create Specifications to search over this table for the attributes inside the bList.
For example, search by an entity that contains a B object where type=TYPE1 and a quantity>=30.
public static Specification<A> customSpecification(String type, Integer value) {
return (root, query, builder) -> ///?????????
}
Is there a way to use such specifications where the DB attribute is a String but JAVA only sees the objects?

hibernate MappingException: Could not determine type using composite key

(Using Spring 3.1 and hibernate 3.3)
I am using an IdClass with an entity that maps to a table that has 3 columns as a composite key.
My tests are failing throwing a runtime exception MappingException complaining that hibernate cannot determine the type for one of my columns used as part for the composite key. in this case it is the set column (aka in the db table as "set_id").
Here is a cut down version of my entity
#Entity
#Table(name = "the_table")
#IdClass(CompositeKey.class)
public class MyEntity {
#Id
#Column(name = "page_id")
private Integer pageId;
#Id
#Column(name = "xml_id")
private Integer xmlId;
#Id
#ManyToOne
#JoinColumn(name = "set_id")
private CustomSet set;
public CustomSet getSet() {
return set;
}
public void setSet(CustomSet set) {
this.set = set;
}
public Integer getPageId() {
return pageId;
}
public void setPageId(Integer pageId) {
this.pageId = pageId;
}
public Integer getXmlId() {
return xmlId;
}
public void setXmlId(Integer xmlId) {
this.xmlId = xmlId;
}
}
Here is the composite key id class
public class CompositeKey implements Serializable {
private Integer pageId;
private Integer xmlId;
private CustomSet set;
public CompositeKey(){}
public CompositeKey(Integer pageId, Integer xmlId, CustomSet set){
this.pageId = pageId;
this.xmlId = xmlId;
this.set = set;
}
public Integer getPageId() {
return pageId;
}
public Integer getXmlId() {
return xmlId;
}
public CustomSet getSet() {
return set;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof CompositeKey)) return false;
CompositeKey that = (CompositeKey) o;
if (!getPageId().equals(that.getPageId())) return false;
if (!getXmlId().equals(that.getXmlId())) return false;
return getSet().equals(that.getSet());
}
#Override
public int hashCode() {
int result = getPageId().hashCode();
result = 31 * result + getXmlId().hashCode();
result = 31 * result + getSet().hashCode();
return result;
}
}
I found that the answer was simple enough, I needed to annotate the "set" column in the composite key class with the same mapping type as in the entity.
Also because I have table column names that are different in to the variable names in the code, I had to add the extra column annotations to the variables in the composite key class as well.
here is the code change I made to the CompositeKey...
#Column(name = "page_id")
private Integer pageId;
#Column(name = "xml_id")
private Integer xmlId;
#ManyToOne
#JoinColumn(name = "set_id")
private CustomSet set;
That's it, now hibernate knows what type of mapping and column names to use with that database table. I had assumed it would pick that all up from the entity, but I guess not.
I had a similar issue, and I solved it by just adding the annotation #ManyToOne and #JoinColumn, to one of the Primary Keys (I was unfortunately missing) on the main class. Posting here just in case someone did the same mistake.
Actually adding annotations for the attributes on the composite key caused errors for me. I don't thinks that would be the correct approach.

How to set enum field as Integer via Hibernate to db?

I have integer column as "status" in my db.
My enum class:
public enum MemberStatus {
PASSIVE(0),ACTIVE(1);
private int value;
private MemberStatus(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
My entity field:
#Column(name = "status", nullable = false)
#Enumerated(EnumType.ORDINAL)
private MemberStatus status;
Hibernate Log:
org.postgresql.util.PSQLException: ERROR: column "status" is of type integer but expression is of type bytea.
Hint: You will need to rewrite or cast the expression.bytea
I use PostgreSQL. How to solve this problem? Any idea?
I suggest you use a converter.
It's the cleanest solution i came to because:
you no longer have an issue with the order in which you add values to
the enum or if you refactor the enum elements name
you have more flexibility on what database type your column has
You can define the field as:
#Column(name = "status", nullable = false)
#Convert(converter = MemberStatusEnumConverter.class)
private MemberStatus status;
The enum becomes simpler:
public enum MemberStatus {
PASSIVE,
ACTIVE;
}
And your converter class MemberStatusEnumConverter:
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
#Converter
public class MemberStatusEnumConverter implements
AttributeConverter<MemberStatus,Integer>{
#Override
public Integer convertToDatabaseColumn(MemberStatus attribute) {
switch (attribute) {
case PASSIVE:
return new Integer(0);
case COUNTYLEVEL:
return new Integer(1);
default:
throw new IllegalArgumentException("Unknown" + attribute);
}
}
#Override
public MemberStatus convertToEntityAttribute(Integer dbData) {
if (dbData == 0){
return MemberStatus.PASSIVE;
} else if (dbData == 1){
return MemberStatus.ACTIVE;
}
else{
throw new IllegalArgumentException("Unknown" + dbData);
}
}
}
This article describes the solution i implemented for your example.

Categories

Resources