What's the equivalent of #Convert in hibernate hbm file? - java

I wrote a an attribute converter. I want to apply that in an entity. I'm following a purely XML approach so far.
I could not find an equivalent of #Convert in hbm notation.
An example would be appreciated.
When I search for this, understandably, Google returns lots of results about tools/methods on "Auto Converting hbm files to entities vice versa".
Edit:
Now I'm suspecting if there is an option in hbm file, given that this is JPA annotation.
The doc of #Convert says:
The Convert annotation is used to specify the conversion of a Basic
field or property. It is not necessary to use the Basic annotation or
corresponding XML element to specify the basic type.
I'm not entirely sure what it means. Is mixing annotation and XML a way to go in this case?
I've tried this:
public class Person {
//this is enum
private Ethnicity ethnicity;
//.....
}
public enum Ethnicity{
INDIAN("IND"),
PERSIAN("PER")
//...constructors and value field.
public String value(){
return this.value;
}
public Ethnicity fromValue(String value){
//logic for conversion
}
}
Converter:
#Converter
public class EthnicityConverter implements AttributeConverter<Ethnicity,String> {
#Override
public Ethnicity convertToEntityAttribute(String attribute) {
if ( attribute == null ) {
return null;
}
return Ethnicity.fromValue( attribute );
}
#Override
public String convertToDatabaseColumn(Ethnicity dbData) {
if ( dbData == null ) {
return null;
}
return dbData.value();
}
}
HBM File:
//....other columns
<property name="ethnicity">
<column name="ethnicity"/>
<type name="EthnicityConverter"/>
</property>
//....other columns
Edit: Corrected the converter code.

The answer from Sarvana is close - you do in fact use the type XML attribute. However, type is used to name a Hibernate Type. However there is a convention to name, instead, an AttributeConverter - simply apply the prefix converted:: to your AttributeConverter FQN. E.g.,
<property name="ethnicity">
<column name="ethnicity"/>
<type name="converted::EthnicityConverter"/>
</property>
The other option is to auto-apply the converter:
#Converter( autoApply=true)
public class EthnicityConverter implements AttributeConverter<Ethnicity,String> {
...
}
Given the converter above, so long as Hibernate knows about it, Hibernate will apply that to any attribute of type Ethnicity.
HTH

type is the equivalent xml attribute for Convert annotation.
Below is to convert to Y/N in DB and Boolean in entity.
<property name="status" column="book_status" type="yes_no" not-null="true"/>
Just replace yes_no with your custom converter class
Please see my answer at
https://stackoverflow.com/a/37914271/3344829
Official documentation
https://docs.jboss.org/hibernate/orm/4.2/manual/en-US/html/ch06.html
Update
<property name="ethnicity" column="ethnicity" type="com.example.EthnicityConverter"/>
Update
#Converter
public class EthnicityConverter implements AttributeConverter<Ethnicity, String> {
#Override
public String convertToDatabaseColumn(Ethnicity attribute) {
// TODO return String value of enum
}
#Override
public Ethnicity convertToEntityAttribute(String dbData) {
// TODO return resolved enum from string
}
}

I too had to face this issue and i was able to resolve it.
Refer : Hibernate 5 Documentation Example 33. HBM mapping for AttributeConverter
We have to use exact class with package.
Ex :
<property name="ethnicity" column="ethnicity" type="converted::com.example.EthnicityConverter"/>

Related

Spring Batch: How to get properties from subclass object from BeanWrapperFieldExtractor

I have an application which takes xml as input and writing down to csv.
Here is my input xml:
<Main location="ABC" date="2018-02-26" name="Default">
<Sub firstname="XYZ" lastname="PQR"/>
<Sub firstname="147" lastname="123"/>
</Main>
I have below in my itemwriter's BeanWrapperFieldExtractor:
<beans:property name="fieldExtractor">
<beans:bean class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor">
<beans:property name="names" value="location,date,name"/>
</beans:bean>
</beans:property>
I'm getting correct output but I want to include firstname and lastname also and i have added below:
<beans:property name="names" value="location,date,name,sub.firstname,sub.lastname"/>
but I'm getting below Errors:
org.springframework.beans.NotReadablePropertyException: Invalid property 'sub.firstname' of bean class [com.xyz.MainClass]: Bean property 'sub.firstname' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
at org.springframework.beans.BeanWrapperImpl.getPropertyValue(BeanWrapperImpl.java:707)
Is there any way to extract whole Sub element from xml data of put in csv file one by one.
Having the same issue, I have found two solutions to retrieve sub properties :
I assume you are mapping your input to its related classes.
1. Using the FieldExtractor, create a custom property + getter on your class to retrieve the child property :
public class Example {
#OneToOne
private ChildExample childExample;
// add Transient if you use hibernate to avoid validation:
#Transient
private String customProperty;
// custom getter :
public String getCustomProperty() {
return childExample.getChildProperty();
}
}
Then you can configure the BeanWrapperFieldExtractor on the writer :
BeanWrapperFieldExtractor<Example> fieldExtractor = new BeanWrapperFieldExtractor<>();
fieldExtractor.setNames(new String[]{"customProperty"});
// then you configure the lineAggregator on a FileItemWriter :
DelimitedLineAggregator<Example> lineAggregator = new DelimitedLineAggregator<>();
lineAggregator.setFieldExtractor(fieldExtractor);
FlatFileItemWriter<Example> writer = new FlatFileItemWriter<>();
writer.setLineAggregator(lineAggregator);
writer.open(executionContext);
...
2. Using a custom LineAggregator and overriding the toString class method :
#Component
public class ExampleLineAggregator<Example> implements LineAggregator<Example> {
#Override
public String aggregate(Example item) {
return item.toString();
}
}
Override the toString method:
public class Example {
#OneToOne
private ChildExample childExample;
#Override
public String toString() {
return childExample.getChildProperty();
}
}
Then set the custom LineAggregator to the writer :
FlatFileItemWriter<Example> writer = new FlatFileItemWriter<>();
writer.setLineAggregator(customLineAggregator);

How to use spring data with couchbase without _class attribute

Is there a simple way to use spring data couchbase with documents that do not have _class attribute?
In the couchbase I have something like this in my sampledata bucket:
{
"username" : "alice",
"created" : 1473292800000,
"data" : { "a": 1, "b" : "2"},
"type" : "mydata"
}
Now, is there any way to define mapping from this structure of document to Java object (note that _class attribute is missing and cannot be added) and vice versa so that I get all (or most) automagical features from spring couchbase data?
Something like:
If type field has value "mydata" use class MyData.java.
So when find is performed instead of automatically adding AND _class = "mydata" to generated query add AND type = "mydata".
Spring Data in general needs the _class field to know what to instantiate back when deserializing.
It's fairly easy in Spring Data Couchbase to use a different field name than _class, by overriding the typeKey() method in the AbsctractCouchbaseDataConfiguration.
But it'll still expect a fully qualified classname in there by default
Getting around that will require quite a bit more work:
You'll need to implement your own CouchbaseTypeMapper, following the model of DefaultCouchbaseTypeMapper. In the super(...) constructor, you'll need to provide an additional argument: a list of TypeInformationMapper. The default implementation doesn't explicitly provide one, so a SimpleTypeInformationMapper is used, which is the one that puts FQNs.
There's an alternative implementation that is configurable so you can alias specific classes to a shorter name via a Map: ConfigurableTypeInformationMapper...
So by putting a ConfigurableTypeInformationMapper with the alias you want for specific classes + a SimpleTypeInformationMapper after it in the list (for the case were you serialize a class that you didn't provide an alias for), you can achieve your goal.
The typeMapper is used within the MappingCouchbaseConverter, which you'll also need to extend unfortunately (just to instantiate your typeMapper instead of the default.
Once you have that, again override the configuration to return an instance of your custom MappingCouchbaseConverter that uses your custom CouchbaseTypeMapper (the mappingCouchbaseConverter() method).
You can achive this e.g. by creating custom annotation #DocumentType
#DocumentType("billing")
#Document
public class BillingRecordDocument {
String name;
// ...
}
Document will look like:
{
"type" : "billing"
"name" : "..."
}
Just create following classes:
Create custom AbstractReactiveCouchbaseConfiguration or AbstractCouchbaseConfiguration (depends which varian you use)
#Configuration
#EnableReactiveCouchbaseRepositories
public class CustomReactiveCouchbaseConfiguration extends AbstractReactiveCouchbaseConfiguration {
// implement abstract methods
// and configure custom mapping convereter
#Bean(name = BeanNames.COUCHBASE_MAPPING_CONVERTER)
public MappingCouchbaseConverter mappingCouchbaseConverter() throws Exception {
MappingCouchbaseConverter converter = new CustomMappingCouchbaseConverter(couchbaseMappingContext(), typeKey());
converter.setCustomConversions(customConversions());
return converter;
}
#Override
public String typeKey() {
return "type"; // this will owerride '_class'
}
}
Create custom MappingCouchbaseConverter
public class CustomMappingCouchbaseConverter extends MappingCouchbaseConverter {
public CustomMappingCouchbaseConverter(final MappingContext<? extends CouchbasePersistentEntity<?>,
CouchbasePersistentProperty> mappingContext, final String typeKey) {
super(mappingContext, typeKey);
this.typeMapper = new TypeBasedCouchbaseTypeMapper(typeKey);
}
}
and custom annotation #DocumentType
#Persistent
#Inherited
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE})
public #interface DocumentType {
String value();
}
Then create TypeAwareTypeInformationMapper which will just check if an entity is annoatated by #DocumentType if so, use value from that annotation, do the default if not (fully qualified class name)
public class TypeAwareTypeInformationMapper extends SimpleTypeInformationMapper {
#Override
public Alias createAliasFor(TypeInformation<?> type) {
DocumentType[] documentType = type.getType().getAnnotationsByType(DocumentType.class);
if (documentType.length == 1) {
return Alias.of(documentType[0].value());
}
return super.createAliasFor(type);
}
}
Then register it as following
public class TypeBasedCouchbaseTypeMapper extends DefaultTypeMapper<CouchbaseDocument> implements CouchbaseTypeMapper {
private final String typeKey;
public TypeBasedCouchbaseTypeMapper(final String typeKey) {
super(new DefaultCouchbaseTypeMapper.CouchbaseDocumentTypeAliasAccessor(typeKey),
Collections.singletonList(new TypeAwareTypeInformationMapper()));
this.typeKey = typeKey;
}
#Override
public String getTypeKey() {
return typeKey;
}
}
In your couchbase configuration class you just need to have :
#Override
public String typeKey() {
return "type";
}
Unfortunately for query derivation (n1ql) the _class or type are still using the class name.Tried spring couch 2.2.6 and it's minus point here.
#Simon, are you aware that something has changed and the support to have the possibility to have custom _class/type value in next release(s)?
#SimonBasle
Inside of class N1qlUtils and method createWhereFilterForEntity we have access to the CouchbaseConverter. On line:
String typeValue = entityInformation.getJavaType().getName();
Why not use the typeMapper from the converter to get the name of the entity when we want to avoid using the class name? Otherwise you have to annotate each method in your repository as follows:
#Query("#{#n1ql.selectEntity} WHERE `type`='airport' AND airportname = $1")
List<Airport> findAirportByAirportname(String airportName);
If createWhereFilterForEntity used the CouchbaseConverter we could avoid annotating with the #Query.

Spring Data Neo4j (SDN) Query Parameter doesn't use Converter

I want to be able to store a Set of fully qualified Class names as a property of a Node. Given this Node:
#NodeEntity
public TestNode {
Set<Class<?>> classSet;
//getters and setters here
}
And the following custom converters:
public class ClassToStringConverter implements Converter<Class<?>, String> {
#Override
public String convert(final Class<?> source) {
return source.getName();
}
}
public class StringToClassConverter implements Converter<String, Class<?>> {
#Override
public String convert(final String source) {
Class<?> returnVal = null;
try {
returnVal = Class.forName(source);
} catch (ClassNotFoundException e) { }
return returnVal;
}
}
I register the converters with Spring context as such:
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="my.package.ClassToStringConverter"/>
<bean class="my.package.StringToClassConverter "/>
</set>
</property>
</bean>
Then using my repository, I can properly save and retrieve the Node, and it stores the fully qualified class name as expected using my custom converters. However, I would like to be able to query by a Class name as such in my repository:
#Query("MATCH (m:TestNode) where {0} in m.classSet return m;")
public findByClassInClassSet(Class<?> clazz);
However, this seems to convert the Class using Class.toString() instead of using my Converter. So it is searching for the String "class my.package.TestNode" instead of what the Converter correctly stored as "my.package.TestNode".
Am I missing something here or doing something wrong? How can I benefit from these converters if I can't query using the Class type?
NOTE: Please excuse any typos - this code is on a disconnected network so I couldn't copy paste. If there are any typos, I assure you that is not the problem on my actual code.
Should be fixed in the next milestone release for derived finders.
For your annotated query-method, SDN cannot know what you are referring to with your parameter, so it will not be able to convert it, ever.

Converting configuration properties to enum values

I have a configuration file which contains this line:
login.mode=PASSWORD
and an enum
public enum LoginMode {
PASSWORD, NOT_PASSWORD, OTHER }
and a spring bean
<bean id="environment" class="a.b.c.Environment" init-method="init">
<property name="loginMode" value="${login.mode}"/>
</bean>
and of course a bean class
public class Environment {
private LoginMode loginMode;
public LoginMode getLoginMode() {
return loginMode;
}
public void setLoginMode(LoginMode loginMode) {
this.loginMode = loginMode;
}
}
How can i convert the property of the configuration file (which is a String) into the corresponding enum value of LoginMode?
EDIT: i know how to get the enum value of a string input, but the issue is another one:
If i try this:
public class Environment {
private LoginMode loginMode;
public LoginMode getLoginMode() {
return loginMode;
}
public void setLoginMode(String loginMode) {
this.loginMode = LoginMode.valueOf(loginMode);
}
}
spring is complaining about getter and setter not having the same input and output type.
Bean property 'loginMode' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
Spring automatically converts input Strings to the corresponding valueOf of the desired enum.
You can do that by
LoginMode.valueOf("someString");
LoginMode.valueOf(valueOfProperty);
EDIT:
Try using converter
http://docs.spring.io/spring/docs/3.0.0.RC2/reference/html/ch05s05.html
http://forum.spring.io/forum/spring-projects/web/83191-custom-enum-string-converters
EDIT2:
also check this:
How assign bean's property an Enum value in Spring config file?

How to use joda time with JPA (eclipselink)?

I tried to use the DataTime in my entity class. Adding #Temporal(TemporalType.DATE) above the field, I got the error saying "The persistent field or property for a Temporal type must be of type java.util.Date, java.util.Calendar or java.util.GregorianCalendar". I can introduce the conversion back and forth; using setters and getters as follows:
#Temporal(TemporalType.DATE)
private Calendar attendanceDate;
public DateTime getAttendanceDate() {
return new DateTime(this.attendanceDate);
}
public void setAttendanceDate(DateTime attendanceDate) {
this.attendanceDate = attendanceDate.toCalendar(Locale.getDefault());
}
but I hope eclipselink to do it for me. I have gone thro' Persist Joda-time's DateTime via Hibernate. The answer suggesting to use hibernate, but I have to use eclipselink. I can use the DateTime object in my entity class with DB representation as BLOB, but I need it as Date. Is there anything like jodatime-eclipselink? Or any other suggestion? Thanks for the help.
Basic the link defines an EclipseLink Converter to convert from Joda DateTime to java.sql.Timestamp or Date.
You could use it, or define your own converter and use it through #Convert, #Converter in EclipseLink.
For DDL creation, the converter should define the initialize method and set the type on the mapping's field to java.sql.Timestamp.
Please log a bug (or vote for the existing one) on this in EclipseLink, we should have support for Joda.
I Try use joda-time-eclipselink-integration, but don't work, problably I made something wrong,
So I made more researchs and i found this link http://jcodehelp.blogspot.com.br/2011/12/persist-joda-datetime-with-eclipselink.html, they use #Converter annotation to convert the Joda Date Time.
I Try and works for me, I hope, works for you too.
I wanted to do the same thing, and Googling around actually led me here. I was able to accomplish this using the #Access annotation. First, you annotate the class like this
#Access(AccessType.FIELD)
public class MyClass
{
....
This provides field access to everything so you don't have to annotate the fields individually. Then you create a getter and setter for the JPA to use.
#Column(name="my_date")
#Temporal(TemporalType.DATE)
#Access(AccessType.PROPERTY)
private Date getMyDateForDB()
{
return myDate.toDate();
}
private void setMyDateForDB(Date myDateFromDB)
{
myDate = new LocalDate(myDateFromDB);
}
The #Access(AccessType.PROPERTY) tells JPA to persist and retrieve through these methods.
Finally, you'll want to mark your member variable as transient as follows
#Transient
private LocalDate myDate = null;
This stops JPA from trying to persist from the field as well.
That should accomplish what you're trying to do. It worked great for me.
Ahamed, you mentioned it wasn't working for you. Additionally you need to override the initialize method of the converter to define the desired field type:
#Override
public void initialize(DatabaseMapping mapping, Session session) {
((AbstractDirectMapping) mapping)
.setFieldClassification(java.sql.Timestamp.class);
}
The following is a working example based on the answers available in the topic
Basically the easiest approach is to use EclipseLink #Converter for a DateTime field in your Entity.
The converter usage looks as follows:
import org.eclipse.persistence.annotations.Convert;
import org.eclipse.persistence.annotations.Converter;
import javax.persistence.*;
import org.joda.time.DateTime;
#Entity
public class YourEntity {
#Converter(name = "dateTimeConverter", converterClass = your.package.to.JodaDateTimeConverter.class)
#Convert("dateTimeConverter")
private DateTime date;
And the converter itself:
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.converters.Converter;
import org.eclipse.persistence.sessions.Session;
import org.joda.time.DateTime;
import java.sql.Timestamp;
public class JodaDateTimeConverter implements Converter {
private static final long serialVersionUID = 1L;
#Override
public Object convertDataValueToObjectValue(Object dataValue, Session session) {
return dataValue == null ? null : new DateTime(dataValue);
}
#Override
public Object convertObjectValueToDataValue(Object objectValue, Session session) {
return objectValue == null ? null : new Timestamp(((DateTime) objectValue).getMillis());
}
#Override
public void initialize(DatabaseMapping mapping, Session session) {
// this method can be empty if you are not creating DB from Entity classes
mapping.getField().setType(java.sql.Timestamp.class);
}
#Override
public boolean isMutable() {
return false;
}
}
I am adding this for the purpose of easy copy-and-paste solution.
Solution is here
joda-time-eclipselink-integration
Answer from Atais works well. Below an upgrade to it.
You can omit #converter annotation by registering it globally.
At persistance.xml in persitence-unit add:
<mapping-file>META-INF/xyz-orm.xml</mapping-file>
and file META-INF/xyz-orm.xml with content:
<?xml version="1.0"?>
<entity-mappings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/orm" version="2.1">
<converter class="pl.ds.wsc.storage.converter.JodaDateTimeConverter"/>
</entity-mappings>
If your config file is META-INF/orm.xml then you can omit even first step because it is default confing for all persitence units.

Categories