Hibernate entities mapping: Retrieve VARCHAR as boolean - java

I have a table INCIDENCIA in my database that has a VARCHAR column VISIBLE with two possible values: Y or N matching true or false.
I have it mapped in this entity:
#Entity
public class Incidencia {
private String visible;
//other fields
#Basic
#Column(name = "VISIBLE")
public String getVisible() {
return visible;
}
public void setVisible(String visible) {
this.visible = visible;
}
}
This field is a String since column in database is a VARCHAR, however I would like to retrieve it as java.lang.Boolean with a Y/N deserialization.
Is there any way to do this by Hibernate annotations?
Thanks.

You can create your own mapping type. Something like this:
package es.buena.jamon.type;
public class SpanishBoolean extends AbstractSingleColumnStandardBasicType<Boolean>
implements PrimitiveType<Boolean>, DiscriminatorType<Boolean>
{
private static final long serialVersionUID = 1L;
public static final SpanishBoolean INSTANCE = new SpanishBoolean();
public SpanishBoolean() {
super( CharTypeDescriptor.INSTANCE, new BooleanTypeDescriptor('S', 'N') );
}
#Override
public String getName() {
return "si_no";
}
#Override
public Class getPrimitiveClass() {
return boolean.class;
}
#Override
public Boolean stringToObject(String xml) throws Exception {
return fromString( xml );
}
#Override
public Serializable getDefaultValue() {
return Boolean.FALSE;
}
#Override
public String objectToSQLString(Boolean value, Dialect dialect) throws Exception {
return StringType.INSTANCE.objectToSQLString( value ? "S" : "N", dialect );
}
}
and then register it with the configuration:
Configuration configuration = new Configuration().configure();
configuration.registerTypeOverride(new SpanishBoolean());
and then use it in your entity:
#Type(type="es.buena.jamon.type.SpanishBoolean")
private Boolean visible;
Hope that helps.

Related

Simple xml returns null value for attribute field

I want to use Simple XML to deserialize the following XML into a POJO:
<shippingInfo>
<shippingServiceCost currencyId="USD">9.8</shippingServiceCost>
<shippingType>Flat</shippingType>
<shipToLocations>Worldwide</shipToLocations>
<expeditedShipping>true</expeditedShipping>
<oneDayShippingAvailable>false</oneDayShippingAvailable>
<handlingTime>3</handlingTime>
</shippingInfo>
I have created the following class to do so. However, I'm having trouble in that the currencyId attribute isn't being properly deserialized.
#Root(name = "shippingInfo")
public class ShippingInfo {
#Element(name = "shippingServiceCost", required = false)
private BigDecimal shippingServiceCost;
#Attribute(name = "currencyId", required = false)
private String currencyId;
#Element(name = "shippingType", required = false)
private String shippingType;
#Element(name = "shipToLocations" ,required = false)
private String shipToLocations;
#Element(name = "expeditedShipping", required = false)
private Boolean expeditedShipping;
#Element(name = "oneDayShippingAvailable", required = false)
private Boolean oneDayShippingAvailable;
#Element(name = "handlingTime", required = false)
private Integer handlingTime;
// Getters & Setters
public BigDecimal getShippingServiceCost() {
return shippingServiceCost;
}
public void setShippingServiceCost(BigDecimal shippingServiceCost) {
this.shippingServiceCost = shippingServiceCost;
}
public String getCurrencyId() {
return currencyId;
}
public void setCurrencyId(String currencyId) {
this.currencyId = currencyId;
}
public String getShippingType() {
return shippingType;
}
public void setShippingType(String shippingType) {
this.shippingType = shippingType;
}
public String getShipToLocations() {
return shipToLocations;
}
public void setShipToLocations(String shipToLocations) {
this.shipToLocations = shipToLocations;
}
public Boolean isExpeditedShipping() {
return expeditedShipping;
}
public void setExpeditedShipping(Boolean bool) {
this.expeditedShipping = bool;
}
public Boolean isOneDayShippingAvailable() {
return oneDayShippingAvailable;
}
public void setOneDayShippingAvailable(Boolean bool) {
this.oneDayShippingAvailable = bool;
}
public Integer getHandlingTime() {
return handlingTime;
}
public void setHandlingTime(Integer days) {
this.handlingTime = days;
}
}
I would expect the value of currencyId to be "USD" after deserializing, but I'm getting null. All the element values appear to deserialize properly. Does anyone have a suggestion on how to fix this?
Moreover, in a case such as the following instance:
<sellingStatus>
<currentPrice currencyId="USD">125.0</currentPrice>
<convertedCurrentPrice currencyId="USD">125.0</convertedCurrentPrice>
<bidCount>2</bidCount>
<sellingState>EndedWithSales</sellingState>
</sellingStatus>
Where there are two attributes named currencyId on two distinct elements, how can I go about deserializing these into separate fields? I have created a similar SellingStatus class but am unsure how to distinguish between the currencyId attributes.
Thank you!
Edit: Per suggestions I tried adding a custom ShippingServiceCost class to ShippingInfo as follows:
#Element(name = "shippingServiceCost", required = false)
private ShippingServiceCost shippingServiceCost;
Which in turn looks like this:
public class ShippingServiceCost {
#Element(name = "shippingServiceCost", required = false)
private BigDecimal shippingServiceCost;
#Attribute(name = "currencyId", required = false)
private String currencyId;
// getters and setters
}
But when I try to access both the shippingServiceCost field and the currencyId field, I get null in every instance (even though I know there is data). Any suggestions are greatly appreciated.
For the above code, SimpleXML expects the currencyId to be present as <shippingInfo currencyId="USD">.
So to solve it, you need to create another class called ShippingServiceCost which will contain the currencyId attribute and the BigDecimal
This will also solve your second query. You can do it by creating two classes CurrentPrice and ConvertedCurrentPrice which will contain the currencyId attribute.
The only working solution is creating a Converter class, see code below:
public class ShippingInfoConverter implements Converter<ShippingInfo> {
#Override
public ShippingInfo read(InputNode inputNode) throws Exception {
ShippingInfo shippingInfo = new ShippingInfo();
InputNode shippingServiceCostNode = inputNode.getNext("shippingServiceCost");
shippingInfo.setShippingServiceCost(new BigDecimal(shippingServiceCostNode.getValue()));
shippingInfo.setCurrencyId(shippingServiceCostNode.getAttribute("currencyId").getValue());
shippingInfo.setShippingType(inputNode.getNext("shippingType").getValue());
shippingInfo.setShipToLocations(inputNode.getNext("shipToLocations").getValue());
shippingInfo.setExpeditedShipping(Boolean.parseBoolean(inputNode.getNext("expeditedShipping").getValue()));
shippingInfo.setOneDayShippingAvailable(Boolean.parseBoolean(inputNode.getNext("oneDayShippingAvailable").getValue()));
shippingInfo.setHandlingTime(Integer.valueOf(inputNode.getNext("handlingTime").getValue()));
return shippingInfo;
}
#Override
public void write(OutputNode outputNode, ShippingInfo shippingInfo) throws Exception {
OutputNode shippingServiceCostNode = outputNode.getChild("shippingServiceCost");
shippingServiceCostNode.setValue(shippingInfo.getShippingServiceCost().toString());
shippingServiceCostNode.setAttribute("currencyId", shippingInfo.getCurrencyId());
outputNode.getChild("shippingType").setValue(shippingInfo.getShippingType());
outputNode.getChild("shipToLocations").setValue(shippingInfo.getShipToLocations());
outputNode.getChild("expeditedShipping").setValue(Boolean.toString(shippingInfo.isExpeditedShipping()));
outputNode.getChild("oneDayShippingAvailable").setValue(Boolean.toString(shippingInfo.isOneDayShippingAvailable()));
outputNode.getChild("handlingTime").setValue(Integer.toString(shippingInfo.getHandlingTime()));
}
}
Note how 'currencyId' is set, using the node's getAttribute method.
shippingInfo.setCurrencyId(shippingServiceCostNode.getAttribute("currencyId").getValue());
Also note how the element 'shippingServiceCost' gets the attribute
shippingServiceCostNode.setAttribute("currencyId", shippingInfo.getCurrencyId());
A few other things are need to get this working, starting with your POJO
#Root(name = "shippingInfo")
#Convert(ShippingInfoConverter.class)
public class ShippingInfo {
#Element(name = "shippingServiceCost", required = false)
private BigDecimal shippingServiceCost;
private String currencyId;
#Element(name = "shippingType", required = false)
private String shippingType;
#Element(name = "shipToLocations" ,required = false)
private String shipToLocations;
#Element(name = "expeditedShipping", required = false)
private Boolean expeditedShipping;
#Element(name = "oneDayShippingAvailable", required = false)
private Boolean oneDayShippingAvailable;
#Element(name = "handlingTime", required = false)
private Integer handlingTime;
// Getters & Setters
public BigDecimal getShippingServiceCost() {
return shippingServiceCost;
}
public void setShippingServiceCost(BigDecimal shippingServiceCost) {
this.shippingServiceCost = shippingServiceCost;
}
public String getCurrencyId() {
return currencyId;
}
public void setCurrencyId(String currencyId) {
this.currencyId = currencyId;
}
public String getShippingType() {
return shippingType;
}
public void setShippingType(String shippingType) {
this.shippingType = shippingType;
}
public String getShipToLocations() {
return shipToLocations;
}
public void setShipToLocations(String shipToLocations) {
this.shipToLocations = shipToLocations;
}
public Boolean isExpeditedShipping() {
return expeditedShipping;
}
public void setExpeditedShipping(Boolean bool) {
this.expeditedShipping = bool;
}
public Boolean isOneDayShippingAvailable() {
return oneDayShippingAvailable;
}
public void setOneDayShippingAvailable(Boolean bool) {
this.oneDayShippingAvailable = bool;
}
public Integer getHandlingTime() {
return handlingTime;
}
public void setHandlingTime(Integer days) {
this.handlingTime = days;
}
}
Adding the line below points SimpleXML to the converter class
#Convert(ShippingInfoConverter.class)
The other change is removing the #Attribute annotation.
One last thing required is that your driver class needs to have AnnotationStrategy enabled
when serialising and deserialing your objects.
Serializer serializer = new Persister(new AnnotationStrategy());

Hibernate createCriteria query with annotation based composite primary key

In my project, I am having trouble writing a createCriteria query with a composite primary key. My Entity class & DAO method are given below -
#Entity
#Table(name="METRICS")
public class Metrics implements Serializable {
private static final long serialVersionUID = -2580493160757497919L;
#EmbeddedId
protected MetricsID metricsID;
#Column(name="PROJ_PERF")
private String proj_perf;
#Column(name="ANALYSIS")
private String analysis;
public String getProj_perf() {
return proj_perf;
}
public void setProj_perf(String proj_perf) {
this.proj_perf = proj_perf;
}
public String getAnalysis() {
return analysis;
}
public void setAnalysis(String analysis) {
this.analysis = analysis;
}
public MetricsID getMetricsID() {
return metricsID;
}
public void setMetricsID(MetricsID metricsID) {
this.metricsID = metricsID;
}
}
#Embeddable
public class MetricsID implements Serializable {
private static final long serialVersionUID = 4691163770334366543L;
#Column(name="PROJECT_ID")
private String project_id;
#Column(name="METRICS_NO")
private int metrics_no;
public String getProject_id() {
return project_id;
}
public void setProject_id(String project_id) {
this.project_id = project_id;
}
public int getMetrics_n0() {
return metrics_no;
}
public void setMetrics_no(int i) {
this.metrics_no = i;
}
}
#Override
#Transactional
public List<Metrics> viewMetrics(String project_id) throws Exception {
List<Metrics> metrics = (List<Metrics>)sessionFactory.getCurrentSession().
createCriteria(Metrics.class).createAlias("metricsID.project_id", "project_id_alias").
add(Restrictions.eqProperty("project_id_alias.project_id", project_id)).list();
return metrics;
}
The error I am getting is - org.hibernate.QueryException: not an association: metricsID.project_id
I searched for several similar examples, and used alias on the suggestion of one of the search results, but it's my first time using an alias. What am I doing wrong?
Why do you need to use an alias? Have you tried to access directly?
Following this example, this code should work
#Override
#Transactional
public List<Metrics> viewMetrics(String project_id) throws Exception {
List<Metrics> metrics =
(List<Metrics>) sessionFactory.getCurrentSession()
.createCriteria(Metrics.class)
.add(Restrictions.eq("metricsID.project_id", project_id))
.list();
return metrics;
}

Using UUID with EclipseLink and PostgreSQL

I want to use the PostgreSQL uuid type for objects' primary keys.
For that I've created a converter (implementing the Converter interface).
Bellow is the relevant code:
#Override
public void initialize(DatabaseMapping mapping, Session session) {
final DatabaseField field;
if (mapping instanceof DirectCollectionMapping) {
field = ((DirectCollectionMapping) mapping).getDirectField();
} else {
field = mapping.getField();
}
field.setSqlType(Types.OTHER);
field.setTypeName("uuid");
field.setColumnDefinition("UUID");
}
Then I've annotated the relevant entity X with the bellow annotations:
#Converter(name="uuidConverter",converterCalss=UUIDConverter.class)
#Convert("uuidConverter")
#Id
public UUID getId()
{
return id;
}
The problem is that I have another class (Y) which has the following definition:
#ManyToOne(targetEntity = X.class)
#JoinColumn(name = "x_id")
public X getX();
Although EclipseLink created the tables as expected it sends a string to the database when trying to insert objects of type Y.
Postgres returns the following error message:
column "id" is of type uuid but expression is of type character varying at character
Any solutions / work around will be appreciated.
I had the same issue with EclipseLink JPA + Postgresql + UUID as primary key.
To solve it, I've merged codes from Github and below link:
https://forums.oracle.com/forums/thread.jspa?messageID=4584157
The below code for UUIDConverter worked for me, though the code surely isn't the best.
public void initialize(DatabaseMapping ARGMapping, Session ARGSession)
{
final DatabaseField Field;
if (ARGMapping instanceof DirectCollectionMapping)
{
Field = ((DirectCollectionMapping) ARGMapping).getDirectField();
}
else
{
Field = ARGMapping.getField();
}
Field.setSqlType(Types.OTHER);
Field.setTypeName("uuid");
Field.setColumnDefinition("UUID");
for (DatabaseMapping m : ARGMapping.getDescriptor().getMappings())
{
assert OneToOneMapping.class.isAssignableFrom(ManyToOneMapping.class);
if (m instanceof OneToOneMapping)
{
for (DatabaseField field : ((OneToOneMapping) m).getForeignKeyFields())
{
field.setSqlType(Types.OTHER);
field.setColumnDefinition("UUID");
field.setTypeName("uuid");
}
}
}
}
I had some issues with EclipseLink JPA 2.1 + Postgresql + UUID as primary key but I find out different solution. I adopted AttributeConverter but I faced a problem with EclipseLink implementation that I resolved with this code:
#javax.persistence.Converter(autoApply = true)
public class PostgresUuidConverter implements AttributeConverter<UUID, Object> {
#Override
public Object convertToDatabaseColumn(UUID uuid) {
PostgresUuid object = new PostgresUuid();
object.setType("uuid");
try {
if (uuid == null) {
object.setValue(null);
} else {
object.setValue(uuid.toString());
}
} catch (SQLException e) {
throw new IllegalArgumentException("Error when creating Postgres uuid", e);
}
return object;
}
#Override
public UUID convertToEntityAttribute(Object dbData) {
if (dbData instanceof String) {
return UUID.fromString(dbData.toString());
} else {
return (UUID) dbData;
}
}
}
public class PostgresUuid extends PGobject implements Comparable<Object> {
private static final long serialVersionUID = 1L;
#Override
public int compareTo(Object arg0) {
return 0;
}
}
As I exaplined in detail in this post http://blog-ungarida.rhcloud.com/persisting-uuid-in-postgresql-using-jpa-eclipselink/
Try checking what the fieldClassification of the mapping is in the initialize method. It might be getting String.class somehow, try setting it to Object.class.
or, field.setType(Object.class)
It seems there is a bug/incompatibility between EclipseLink and PostgresQL. If you just use UUID for primary keys you should be okay. But if you have a nullable UUID column, and you try to store null in it, you will get the reported error:
column "whatever" is of type uuid but expression is of type character varying
See: https://bugs.eclipse.org/bugs/show_bug.cgi?id=538138 (log in and vote for it if you have the time!)
That bug report proved very useful to me. Specifically the link to the forum thread at:
https://www.eclipse.org/forums/index.php?t=msg&th=1073632&goto=1719530&#msg_1719530
I tried all sorts of solutions from here on SO, and elsewhere on the web. The only one that seemed to work for me was the one posted by David Wheeler there. Specifically, creating a cast from character varying to uuid in the database.
Note that you have to be user postgres to create the cast:
$ sudo su - postgres
$ psql <your database name>
# drop cast if exists (character varying as uuid);
# create or replace function uuid(_text character varying) returns uuid language sql as 'select uuid_in(_text::cstring)';
# create cast (character varying as uuid) with function uuid(character varying) as assignment;
For completeness here is the rest of what I use (in case it helps)
All my entities (that have a UUID primary key) extend a base class called EntityBase:
package com.example.entity;
import java.io.Serializable;
import java.util.UUID;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import org.eclipse.persistence.annotations.Convert;
import org.eclipse.persistence.annotations.Converter;
import com.example.converter.UUIDTypeConverter;
#MappedSuperclass
#Converter(name="uuidConverter", converterClass=UUIDTypeConverter.class)
public class EntityBase implements Serializable, Cloneable
{
private static final long serialVersionUID = 1L;
#Id
#Convert("uuidConverter")
private UUID id;
public EntityBase() {
this.id = UUID.randomUUID();
}
#Override
public int hashCode() {
return id.hashCode();
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof EntityBase)) {
return false;
}
EntityBase other = (EntityBase) obj;
return getId().equals(other.getId());
}
public UUID getId()
{
return this.id;
}
public void setId(UUID id)
{
this.id = id;
}
}
The UUID converter class looks like this:
package com.example.converter;
import java.sql.Types;
import java.util.UUID;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.converters.Converter;
import org.eclipse.persistence.sessions.Session;
public class UUIDTypeConverter implements Converter
{
#Override
public UUID convertObjectValueToDataValue(Object objectValue, Session session)
{
return (UUID) objectValue;
}
#Override
public UUID convertDataValueToObjectValue(Object dataValue, Session session)
{
return (UUID) dataValue;
}
#Override
public boolean isMutable()
{
return true;
}
#Override
public void initialize(DatabaseMapping mapping, Session session)
{
DatabaseField field = mapping.getField();
field.setSqlType(Types.OTHER);
field.setTypeName("java.util.UUID");
field.setColumnDefinition("UUID");
}
}
If you have entities that have UUID columns that are not primary keys, you can annotate them as follows:
import org.eclipse.persistence.annotations.Convert
import org.eclipse.persistence.annotations.Converter;
#Entity
#Converter(name="uuidConverter", converterClass=UUIDTypeConverter.class)
public class BillingEvent extends EntityBase
{
#Convert("uuidConverter")
private UUID entityId;
}
Note that if that entity has other columns that use the standard javax.persistence.convert annotation, you'll need to differentiate the two Convert annotations to avoid a compile error.
For example:
import javax.persistence.Convert;
import org.eclipse.persistence.annotations.Converter;
#Entity
#Converter(name="uuidConverter", converterClass=UUIDTypeConverter.class)
public class BillingEvent extends EntityBase
{
#org.eclipse.persistence.annotations.Convert("uuidConverter")
private UUID entityId;
#Convert(converter = JSR310InstantTypeConverter.class)
private Instant createdOn;
}
I hope this saves others some time. Good luck!
Universal UUIDConverter for EclipseLink (not only PostgreSQL)
Code:
import java.nio.ByteBuffer;
import java.util.UUID;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.DirectCollectionMapping;
import org.eclipse.persistence.mappings.converters.Converter;
import org.eclipse.persistence.sessions.Session;
public class UUIDConverter implements Converter {
private Boolean isUUIDasByteArray = true;
#Override
public Object convertObjectValueToDataValue(Object objectValue,
Session session) {
if (isUUIDasByteArray) {
UUID uuid = (UUID)objectValue;
if (uuid == null) return null;
byte[] buffer = new byte[16];
ByteBuffer bb = ByteBuffer.wrap(buffer);
bb.putLong(uuid.getMostSignificantBits());
bb.putLong(uuid.getLeastSignificantBits());
return buffer;
}
return objectValue;
}
#Override
public UUID convertDataValueToObjectValue(Object dataValue,
Session session) {
if (isUUIDasByteArray) {
byte[] bytes = (byte[])dataValue;
if (bytes == null) return null;
ByteBuffer bb = ByteBuffer.wrap(bytes);
long high = bb.getLong();
long low = bb.getLong();
return new UUID(high, low);
}
return (UUID) dataValue;
}
#Override
public boolean isMutable() {
return true;
}
#Override
public void initialize(DatabaseMapping mapping, Session session) {
final DatabaseField field;
if (mapping instanceof DirectCollectionMapping) {
// handle #ElementCollection...
field = ((DirectCollectionMapping) mapping).getDirectField();
} else {
field = mapping.getField();
}
if (session != null && session.getLogin()!= null && session.getLogin().getPlatform() != null) {
String platform = session.getLogin().getPlatform().getClass().getSimpleName();
if (platform.equals("PostgreSQLPlatform")) {
field.setSqlType(java.sql.Types.OTHER);
field.setTypeName("java.util.UUID");
field.setColumnDefinition("UUID");
isUUIDasByteArray = false;
} else if (platform.equals("H2Platform")) {
field.setColumnDefinition("UUID");
} else if (platform.equals("OraclePlatform")) {
field.setColumnDefinition("RAW(16)");
} else if (platform.equals("MySQLPlatform")) {
field.setColumnDefinition("BINARY(16)");
} else if (platform.equals("SQLServerPlatform")) {
field.setColumnDefinition("UNIQUEIDENTIFIER");
}
}
}
}
You don't need a converted. Use this column definition in the entity. You need to register the uuid extension first. This works with Postgres 10 and Wildfly 10.1
#Column(name = "UUID", nullable=false, insertable = false, columnDefinition="uuid DEFAULT uuid_generate_v4()")
private String uuid;

Hibernate annotation object mapping

I'm pretty new with hibernate, and I'm trying to transform a JDBC project I have into Hibernate.
I'm using annotations, and I managed to annotate the basic stuff, however, I'm stuck now with the more heavy objects, I don't know how to annotate them.
Here's the Class:
#Entity
#Table(name = "person")
public class Person {
public Person{
}
// THIS WILL BE SOON INJECTED BY SPRING
private static transient PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
private static transient EmailValidator validator = EmailValidator.getInstance();
#Id
#Column(name = "person_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "private_name", nullable = false, length = 20)
private String privateName;
#Column(name = "middle_name", length = 20)
private String middleName;
#Column(name = "family_name", nullable = false, length = 20)
private String familyName;
#Column(name = "age", nullable = false)
private int age;
#Column(name = "address1", nullable = false)
private String address1;
#Column(name = "address2")
private String address2;
//How do I annotate this ? --> Google LIBPHONENUMBER
private PhoneNumber phone;
// How do I annotate this ? --> This is a normal PNG image file.
private File image;
Edit:
The File was previously mapped as a BLOB.
The PhoneNumber was previously persisted as String, and was transformed using the PhoneNumber constructor to Phonenumber.
The other comments about using #Lob are correct for the File type. It is also correct that if you can change the schema to not save the file data in the DB, then you probably should.
To map your PhoneNumber class to a database field, you're going to need to use a Hibernate custom UserType. It basically tells Hibernate HOW to do the object<-->db mapping for classes that it doesn't already know about. Telling the PhoneNumber field in Person to use a custom user type is easy:
#Type(type = PhoneNumberType.CLASS_NAME)
#Column
private PhoneNumber phone;
This assumes a very simple one-column storage of the phone number.
To write PhoneNumberType, you'll need to implement UserType. It looks overwhelming, with the assemble/disassemble/deepCopy, but the main part you care about is nullSetGet/Set, returnedClass and sqlTypes. You'll end up with some code like this inside your custom type:
#Override
public Class<?> returnedClass() {
return PhoneNumber.class;
}
#Override
public int[] sqlTypes() {
return new int[] { Types.VARCHAR };
}
#Override
public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {
final String value = rs.getString(names[0]);
return /* PhoneNumber instance created from string. */
}
#Override
public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
if (value == null) {
st.setNull(index, Types.VARBINARY);
return;
}
st.setString(index, ((PhoneNumber) value).toString());
}
You can find plenty of information about how to implement the other methods via google, stackoverflow and the hibernate javadocs. It isn't that hard to do.
UPDATE: Multi-column user type
Implement CompositeUserType instead of just UserType. There are a few method changes that you care about. First you'll want to define the multiple property names and types:
public String[] getPropertyNames() {
return new String[] { "number", "code" };
}
public Type[] getPropertyTypes() {
return new Type[] { StandardBasicTypes.STRING,
StandardBasicTypes.STRING };
}
There's also getPropertyValue/setPropertyValue to implement. Your nullSafeXxxx implementations would change to read and write two properties instead of one:
#Override
public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {
// Access column in order defined in getPropertyNames()
final String number = rs.getString(names[0]);
final String code = rs.getString(names[1]);
return /* PhoneNumber instance created from number and country code. */
}
Personally, I'd store only the filename in the object, and keep the file on the filesystem, where files belong.
Otherwise, map it as a Hibernate blob (#Lob) and you'd want it to be a byte array (would translate to a blob).
IMO this usually creates more trouble than it's worth, but that depends partially on the DB, driver revision, etc.
Just create a Hibernate UserType for PhoneNumber
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import org.apache.commons.lang.ObjectUtils;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.type.StringRepresentableType;
import org.hibernate.usertype.UserType;
import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import com.tg.util.TGPhoneUtils;
public class PhoneNumberUserType implements UserType, StringRepresentableType<PhoneNumber>, Serializable {
private static final long serialVersionUID = -364436436346432L;
#Override
public boolean equals(Object x, Object y) throws HibernateException {
return ObjectUtils.equals(x, y);
}
#Override
public int hashCode(Object object) throws HibernateException {
return object.hashCode();
}
#Override
public Object deepCopy(Object value) throws HibernateException {
return value;
}
#Override
public boolean isMutable() {
return false;
}
#Override
public Serializable disassemble(Object value) throws HibernateException {
return (Serializable) value;
}
#Override
public Object assemble(Serializable cached, Object value) throws HibernateException {
return cached;
}
#Override
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return original;
}
#Override
public String toString(PhoneNumber value) throws HibernateException {
return value.toString();
}
#Override
public Class<?> returnedClass() {
return PhoneNumber.class;
}
#Override
public int[] sqlTypes() {
return new int[] { Types.VARCHAR };
}
#Override
public PhoneNumber fromStringValue(String number) throws HibernateException {
try {
return PhoneNumberUtil.getInstance().parse(number, "US");
} catch (NumberParseException e) {
throw new HibernateException(e);
}
}
#Override
public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor arg2, Object owner) throws HibernateException, SQLException {
final String number = rs.getString(names[0]);
if (number == null) {
return null;
}
return TGPhoneUtils.parsePhoneNumber(number);
}
#Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor si) throws HibernateException, SQLException {
if (value == null) {
st.setNull(index, Types.VARCHAR);
return;
}
st.setString(index, TGPhoneUtils.formatPhoneNumber((PhoneNumber)value));
}
}
and then here is the helper class
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
public class TGPhoneUtils {
public static PhoneNumber parsePhoneNumber(String phoneNum) {
if (phoneNum == null) {
return null;
}
try {
return PhoneNumberUtil.getInstance().parse(phoneNum, "US");
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static String formatPhoneNumber(PhoneNumber phoneNum) {
if (phoneNum == null) {
return null;
}
return PhoneNumberUtil.getInstance().format(phoneNum, PhoneNumberFormat.E164);
}
}
You can annotate PhoneNumber like this:
#ManyToOne
#JoinColumn(name = "PHONE_NUMBER")
private PhoneNumber phone;
Assuming that the column PHONE_NUMBER exists and maps to the id of a phone number. The class PhoneNumber will also need to be annotated. This assumes that you want to maybe share a phone number among different entities (Many to one).
Regarding file, you probably need to decide if you want to actually store the file data in the db (normally not a good idea). Otherwise you could just store a String with a path to file.

Mapping array with Hibernate

Can you please help me to map this class using Hibernate?
public class MyClass{
private Long id;
private String name;
private int[] values;
...
}
I'm using PostgreSQL and the column type in the table is integer[]
How my array should be mapped?
Hibernate (and JPA) can't directly map the PostgreSQL array type. See this question for how to proceed if you really need to retain your database structure as it is. This thread has an example of the required custom type.
If you can change your schema, you can let hibernate create an additional table to handle the collection - List<Integer>. Then, depending on the version of hibernate you are using:
JPA 2.0 compliant - use #ElementCollection
JPA 1.0 compliant - use #CollectionOfElements
Hibernate can map only the primitive types. Check under the org.hibernate.type folder of hibernate jar package. int array is not one of them. So you would have to write a custom type that can implement the UserType interface.
public class MyClass{
private Long id;
private String name;
private Integer[] values;
#Type(type = "com.usertype.IntArrayUserType")
public Integer[] getValues(){
return values;
}
public void setValues(Integer[] values){
this.values = values;
}
}
IntArrayUserType.class
package com.usertype.IntArrayUserType;
public class IntArrayUserType implements UserType {
protected static final int[] SQL_TYPES = { Types.ARRAY };
#Override
public Object assemble(Serializable cached, Object owner) throws HibernateException {
return this.deepCopy(cached);
}
#Override
public Object deepCopy(Object value) throws HibernateException {
return value;
}
#Override
public Serializable disassemble(Object value) throws HibernateException {
return (Integer[]) this.deepCopy(value);
}
#Override
public boolean equals(Object x, Object y) throws HibernateException {
if (x == null) {
return y == null;
}
return x.equals(y);
}
#Override
public int hashCode(Object x) throws HibernateException {
return x.hashCode();
}
#Override
public boolean isMutable() {
return true;
}
#Override
public Object nullSafeGet(ResultSet resultSet, String[] names, SessionImplementor session, Object owner)
throws HibernateException, SQLException {
if (resultSet.wasNull()) {
return null;
}
if(resultSet.getArray(names[0]) == null){
return new Integer[0];
}
Array array = resultSet.getArray(names[0]);
Integer[] javaArray = (Integer[]) array.getArray();
return javaArray;
}
#Override
public void nullSafeSet(PreparedStatement statement, Object value, int index, SessionImplementor session)
throws HibernateException, SQLException {
Connection connection = statement.getConnection();
if (value == null) {
statement.setNull(index, SQL_TYPES[0]);
} else {
Integer[] castObject = (Integer[]) value;
Array array = connection.createArrayOf("integer", castObject);
statement.setArray(index, array);
}
}
#Override
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return original;
}
#Override
public Class<Integer[]> returnedClass() {
return Integer[].class;
}
#Override
public int[] sqlTypes() {
return new int[] { Types.ARRAY };
}
When you query for the MyClass entity you can add something like this:
Type intArrayType = new TypeLocatorImpl(new TypeResolver()).custom(IntArrayUserType.class);
Query query = getSession().createSQLQuery("select values from MyClass")
.addScalar("values", intArrayType);
List<Integer[]> results = (List<Integer[]>) query.list();
Maven dependency
The first thing you need to do is to set up the following Hibernate Types Maven dependency in your project pom.xml configuration file:
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>${hibernate-types.version}</version>
</dependency>
Maven ARRAY columns
Assuming you have this table in your database:
create table event (
id int8 not null,
version int4,
sensor_names text[],
sensor_values integer[],
primary key (id)
)
And you want to map it like this:
#Entity(name = "Event")
#Table(name = "event")
#TypeDefs({
#TypeDef(
name = "string-array",
typeClass = StringArrayType.class
),
#TypeDef(
name = "int-array",
typeClass = IntArrayType.class
)
})
public static class Event extends BaseEntity {
#Type( type = "string-array" )
#Column(
name = "sensor_names",
columnDefinition = "text[]"
)
private String[] sensorNames;
#Type( type = "int-array" )
#Column(
name = "sensor_values",
columnDefinition = "integer[]"
)
private int[] sensorValues;
//Getters and setters omitted for brevity
}
The string-array and int-array are custom types which can be defined in the BaseEntity superclass:
#TypeDefs({
#TypeDef(
name = "string-array",
typeClass = StringArrayType.class
),
#TypeDef(
name = "int-array",
typeClass = IntArrayType.class
)
})
#MappedSuperclass
public class BaseEntity {
#Id
private Long id;
#Version
private Integer version;
//Getters and setters omitted for brevity
}
The StringArrayType and IntArrayType are classes offered by the Hibernate Types project.
Testing time
Now, when you insert a couple of entities;
Event nullEvent = new Event();
nullEvent.setId(0L);
entityManager.persist(nullEvent);
Event event = new Event();
event.setId(1L);
event.setSensorNames(
new String[] {
"Temperature",
"Pressure"
}
);
event.setSensorValues(
new int[] {
12,
756
}
);
entityManager.persist(event);
Hibernate is going to generate the following SQL statements:
INSERT INTO event (
version,
sensor_names,
sensor_values,
id
)
VALUES (
0,
NULL(ARRAY),
NULL(ARRAY),
0
)
INSERT INTO event (
version,
sensor_names,
sensor_values,
id
)
VALUES (
0,
{"Temperature","Pressure"},
{"12","756"},
1
)
I have never mapped arrays to hibernate. I always use collections. So, I have slightly changed you class:
public class MyClass{
private Long id;
private String name;
private List<Integer> values;
#Id
// this is only if your id is really auto generated
#GeneratedValue(strategy=GenerationType.AUTO)
public Long getId() {
return id;
}
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY)
public List<Integer> getValues() {
return values;
}
...
From Hibernate 6.1 Final, basic arrays and collections may now be mapped to database ARRAY types if possible, or alternatively JSON/XML types.
https://in.relation.to/2022/06/14/orm-61-final/

Categories