Dozer mapping/updating a collection - java

I am trying to map a A-DTO object to an A-DO object, each having a collection (a List) of T-DTOs, and T-DOs, respectively. I am trying to do it in the context of a REST API. It's a separate question whether it's a right approach - the problem I'm solving is a case of update. Basically, if one of the T-DTOs inside the A-DTO changes, I want that change to be mapped into the corresponding T-DO inside the A-DO.
I found relationship-type="non-cumulative" in Dozer documentation, so that the object inside the collection is updated, if present. But I end up with Dozer inserting a new T-DO into the A-DO's collection!
NOTE: I did implement equals! it is based on the primary key only for now.
Any ideas?
PS: and, if you think this is a bad idea to handle updates to a one-to-many dependent entity, feel free to point that out.. I'm not 100% sure I like that approach, but my REST foo is not very strong.
UPDATE
equals implementation:
#Override
public boolean equals(Object obj) {
if (obj instanceof MyDOClass) {
MyDOClass other = (MyDOClass) obj;
return other.getId().equals(this.getId());
}
return false;
}

I just had the same problem and I solved it:
Dozer uses contains to determine if a member is inside a collection.
You should implement hashCode so that "contains" will work appropriately.
You can see this in the following documentation page:
http://dozer.sourceforge.net/documentation/collectionandarraymapping.html
Under: "Cumulative vs. Non-Cumulative List Mapping (bi-directional)"
Good luck!

Ended up doing a custom mapping.

I did endup doing my own AbstractConverter please find it below:
It has some constraints which are suitable for me (possibly not for you).
will update based on "sameId" implementation
will remove orphans (element from destination not in the source).
Only works on List (enough for my needs).
While the converter will manage the decision to update the mapping of objects are delegated back to Dozer so you don't need to implement the mapping of the elements in your list
Sample use
public class MyConverter extends AbstractListConverter<ClassX,ClassY>{
public MyConverter(){ super(ClassX.class, ClassY.class);}
#Override
protected boolean sameId(ClassX o1, ClassY o2) {
return // your custom comparison here... true means the o2 and o1 can update each other.
}
}
Declaration in mapper.xml
<mapping>
<class-a>x.y.z.AClass</class-a>
<class-b>a.b.c.AnotherClass</class-b>
<field custom-converter="g.e.MyConverter">
<a>ListField</a>
<b>OtherListField</b>
</field>
</mapping>
public abstract class AbstractListConverter<A, B> implements MapperAware, CustomConverter {
private Mapper mapper;
private Class<A> prototypeA;
private Class<B> prototypeB;
#Override
public void setMapper(Mapper mapper) {
this.mapper = mapper;
}
AbstractListConverter(Class<A> prototypeA, Class<B> prototypeB) {
this.prototypeA = prototypeA;
this.prototypeB = prototypeB;
}
#Override
public Object convert(Object destination, Object source, Class<?> destinationClass, Class<?> sourceClass) {
if (destinationClass == null || sourceClass == null || source == null) {
return null;
}
if (List.class.isAssignableFrom(sourceClass) && List.class.isAssignableFrom(destinationClass)) {
if (destination == null || ((List) destination).size() == 0) {
return produceNewList((List) source, destinationClass);
}
return mergeList((List) source, (List) destination, destinationClass);
}
throw new Error("This specific mapper is only to be used when both source and destination are of type java.util.List");
}
private boolean same(Object o1, Object o2) {
if (prototypeA.isAssignableFrom(o1.getClass()) && prototypeB.isAssignableFrom(o2.getClass())) {
return sameId((A) o1, (B) o2);
}
if (prototypeB.isAssignableFrom(o1.getClass()) && prototypeA.isAssignableFrom(o2.getClass())) {
return sameId((A) o2, (B) o1);
}
return false;
}
abstract protected boolean sameId(A o, B t);
private List mergeList(List source, List destination, Class<?> destinationClass) {
return (List)
source.stream().map(from -> {
Optional to = destination.stream().filter(search -> same(from, search)).findFirst();
if (to.isPresent()) {
Object ret = to.get();
mapper.map(from, ret);
return ret;
} else {
return create(from);
}
}
).collect(Collectors.toList());
}
private List produceNewList(List source, Class<?> destinationClass) {
if (source.size() == 0) return source;
return (List) source.stream().map(o -> create(o)).collect(Collectors.toList());
}
private Object create(Object o) {
if (prototypeA.isAssignableFrom(o.getClass())) {
return mapper.map(o, prototypeB);
}
if (prototypeB.isAssignableFrom(o.getClass())) {
return mapper.map(o, prototypeA);
}
return null;
}
}

Related

Filter a java stream for distinct objects distinguishable according to an attribute without using collections

i have a stream of objects (Measurements) including a private sensorID attribute. I want to filter this stream so i have only Measurements with different sensorIDs. To my understanding the "distinct" method would do that for the objects but not for a specific attribute of the objects. This code does the job by using Collections:
public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
Map<Object, Boolean> seen = new ConcurrentHashMap<>();
return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
and can be used like this
stream.filter(distinctByKey((Measurement p) -> p.getSensorId()))
for the Measurement class
public class Measurement {
private final int sensorId;
//more stuff
public Measurement(int sensorId) {
this.sensorId = sensorId;
//more stuff
}
public int getSensorId() {
return sensorId;
}
}
What i am looking for is a way to do the same thing without using collections or atleast with immutable datatypes. Any idears?
The solution, as suggested by #Deadpool in his comment, was simple.
Just override the equals Methode of the Object.
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Measurement other = (Measurement) obj;
return Objects.equals(sensorID, other.sensorID);
}
but you also have to override the hashCode Methode like this
#Override
public int hashCode() {
return Objects.hash(sensorID);
}
Thats why it didnt work in the beginning.
A possible alternative could be the usage of a collection to map, like the following example:
Collection<Measurement> measurementsById = stream.
.collect(Collectors.toMap(Measurement::getSensorId, Function.identity(),
(measurement1, measurement2) -> measurement1))
.values()
In doing so, you are collecting the stream elements into a map keyed by sensorId. Since a map can have only one value for a key, you are simply selecting the first stream object for each key.
Then, given the result map, by calling values() method you get the list of distinct measurements you grouped by sensorId.

ModelMapper handling java 8 Optional<MyObjectDto> fields to Optional<MyObject>

I've been using modelmapper and java 8 Optionals all around the application which was working fine because they were primitive types; until I changed one of my model objects' field to Optional type. Then all hell broke loose. Turns out many libraries cannot handle generics very well.
Here is the structure
public class MyObjectDto
{
private Optional<MySubObjectDto> mySubObject;
}
public MyObject
{
privae Optional<MySubjObject> mySubObject;
}
When I attempt to map MyObjectDto to MyObject, modelmapper calls
public void setMySubObject(Optional<MySubObject> mySubObject){
this.mySubObject = mySubObject;
}
with Optional<MySubObjectDto>, which I don't understand how that's even possible (there is no inheritance between them). Of course that crashes fast. For now I've changed my setters to accept Dto type just to survive the day but that's not going to work on the long run. Is there a better way to get around this, or shall I create an issue?
So I digged into the modelmapper code and have done this looking at some generic implementations:
modelMapper.createTypeMap(Optional.class, Optional.class).setConverter(new OptionalConverter());
public class OptionalConverter implements ConditionalConverter<Optional, Optional> {
public MatchResult match(Class<?> sourceType, Class<?> destinationType) {
if (Optional.class.isAssignableFrom(destinationType)) {
return MatchResult.FULL;
} else {
return MatchResult.NONE;
}
}
private Class<?> getElementType(MappingContext<Optional, Optional> context) {
Mapping mapping = context.getMapping();
if (mapping instanceof PropertyMapping) {
PropertyInfo destInfo = ((PropertyMapping) mapping).getLastDestinationProperty();
Class<?> elementType = TypeResolver.resolveArgument(destInfo.getGenericType(),
destInfo.getInitialType());
return elementType == TypeResolver.Unknown.class ? Object.class : elementType;
} else if (context.getGenericDestinationType() instanceof ParameterizedType) {
return Types.rawTypeFor(((ParameterizedType) context.getGenericDestinationType()).getActualTypeArguments()[0]);
}
return Object.class;
}
public Optional<?> convert(MappingContext<Optional, Optional> context) {
Class<?> optionalType = getElementType(context);
Optional source = context.getSource();
Object dest = null;
if (source != null && source.isPresent()) {
MappingContext<?, ?> optionalContext = context.create(source.get(), optionalType);
dest = context.getMappingEngine().map(optionalContext);
}
return Optional.ofNullable(dest);
}
}

How to find duplicates in an ArrayList<Object>?

This is a pretty common question, but I could not find this part:
Say I have this array list:
List<MyDataClass> arrayList = new List<MyDataClass>;
MyDataClass{
String name;
String age;
}
Now, I need to find duplicates on the basis of age in MyDataClass and remove them. How is it possible using something like HashSet as described here?
I guess, we will need to overwrite equals in MyDataClass?
But, what if I do not have the luxury of doing that?
And How does HashSet actually internally find and does not add duplicates? I saw it's implementation here in OpenJDK but couldn't understand.
I'd suggest that you override both equals and hashCode (HashSet relies on both!)
To remove the duplicates you could simply create a new HashSet with the ArrayList as argument, and then clear the ArrayList and put back the elements stored in the HashSet.
class MyDataClass {
String name;
String age;
#Override
public int hashCode() {
return name.hashCode() ^ age.hashCode();
}
#Override
public boolean equals(Object obj) {
if (!(obj instanceof MyDataClass))
return false;
MyDataClass mdc = (MyDataClass) obj;
return mdc.name.equals(name) && mdc.age.equals(age);
}
}
And then do
List<MyDataClass> arrayList = new ArrayList<MyDataClass>();
Set<MyDataClass> uniqueElements = new HashSet<MyDataClass>(arrayList);
arrayList.clear();
arrayList.addAll(uniqueElements);
But, what if I do not have the luxury of doing that?
Then I'd suggest you do some sort of decorator-class that does provide these methods.
class MyDataClassDecorator {
MyDataClass mdc;
public MyDataClassDecorator(MyDataClass mdc) {
this.mdc = mdc;
}
#Override
public int hashCode() {
return mdc.name.hashCode() ^ mdc.age.hashCode();
}
#Override
public boolean equals(Object obj) {
if (!(obj instanceof MyDataClassDecorator))
return false;
MyDataClassDecorator mdcd = (MyDataClassDecorator) obj;
return mdcd.mdc.name.equals(mdc.name) && mdcd.mdc.age.equals(mdc.age);
}
}
And if you are not able to override "MyDataClass"'s hashCode and equals methods you could write a wrapper class that handles this.
Suppose that you have a class named Person that has two property: id , firstName.
write this mehod in its class:
String uniqueAttributes() {
return id + firstName;
}
The getDuplicates() method is now should be such as:
public static List<Person> getDuplicates(final List<Person> personList) {
return getDuplicatesMap(personList).values().stream()
.filter(duplicates -> duplicates.size() > 1)
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
private static Map<String, List<Person>> getDuplicatesMap(List<Person> personList) {
return personList.stream().collect(groupingBy(Person::uniqueAttributes));
}
public Set<Object> findDuplicates(List<Object> list) {
Set<Object> items = new HashSet<Object>();
Set<Object> duplicates = new HashSet<Object>();
for (Object item : list) {
if (items.contains(item)) {
duplicates.add(item);
} else {
items.add(item);
}
}
return duplicates;
}

Dozer bidirectional mapping (String, String) with custom comverter impossible?

I have a Dozer mapping with a custom converter:
<mapping>
<class-a>com.xyz.Customer</class-a>
<class-b>com.xyz.CustomerDAO</class-b>
<field custom-converter="com.xyz.DozerEmptyString2NullConverter">
<a>customerName</a>
<b>customerName</b>
</field>
</mapping>
And the converter:
public class DozerEmptyString2NullConverter extends DozerConverter<String, String> {
public DozerEmptyString2NullConverter() {
super(String.class, String.class);
}
public String convertFrom(String source, String destination) {
String ret = null;
if (source != null) {
if (!source.equals(""))
{
ret = StringFormatter.wildcard(source);
}
}
return ret;
}
public String convertTo(String source, String destination) {
return source;
}
}
When I call the mapper in one direction (Customer -> CustomerDAO) the method 'convertTo' is called.
Since Dozer is able to handle bi-directional mapping, I expect that, as soon as I call the mapper in the opposite direction, the method 'convertFrom' will be called.
But the method convertTo is never called.
I suspect that the problem is, that both types are Strings - but how can I make this work?
As a workaround I created two one-way-mapping, is this the standard solution, or is the behavior a bug?
Yes, the problem is that your source and destination classes are the same. Here is the dozer source for DozerConverter:
public Object convert(Object existingDestinationFieldValue, Object sourceFieldValue, Class<?> destinationClass, Class<?> sourceClass) {
Class<?> wrappedDestinationClass = ClassUtils.primitiveToWrapper(destinationClass);
Class<?> wrappedSourceClass = ClassUtils.primitiveToWrapper(sourceClass);
if (prototypeA.equals(wrappedDestinationClass)) {
return convertFrom((B) sourceFieldValue, (A) existingDestinationFieldValue);
} else if (prototypeB.equals(wrappedDestinationClass)) {
return convertTo((A) sourceFieldValue, (B) existingDestinationFieldValue);
} else if (prototypeA.equals(wrappedSourceClass)) {
return convertTo((A) sourceFieldValue, (B) existingDestinationFieldValue);
} else if (prototypeB.equals(wrappedSourceClass)) {
return convertFrom((B) sourceFieldValue, (A) existingDestinationFieldValue);
} else if (prototypeA.isAssignableFrom(wrappedDestinationClass)) {
return convertFrom((B) sourceFieldValue, (A) existingDestinationFieldValue);
} else if (prototypeB.isAssignableFrom(wrappedDestinationClass)) {
return convertTo((A) sourceFieldValue, (B) existingDestinationFieldValue);
} else if (prototypeA.isAssignableFrom(wrappedSourceClass)) {
return convertTo((A) sourceFieldValue, (B) existingDestinationFieldValue);
} else if (prototypeB.isAssignableFrom(wrappedSourceClass)) {
return convertFrom((B) sourceFieldValue, (A) existingDestinationFieldValue);
} else {
throw new MappingException("Destination Type (" + wrappedDestinationClass.getName()
+ ") is not accepted by this Custom Converter ("
+ this.getClass().getName() + ")!");
}
}
Instead of using the convertFrom and convertTo methods (which are part of the new API), do it the original way in you have to implement CustomConverter.convert as shown in the tutorial.
I had the same problem and currently (as of Dozer 5.5.x) there's no simple way, but there is complex one.
Note, that it relies on having no security manager enabled in JVM, or else you will need to add few permissions in the security rules. That's because this solution uses reflection to access private fields of Dozer classes.
You need to extend 2 classes: DozerBeanMapper and MappingProcessor. You will also need enum for direction and interface to get direction from above classes.
The enum:
public enum Direction {
TO,
FROM;
}
The interface:
public interface DirectionAware {
Direction getDirection();
}
The class extending DozerBeanMapper:
public class DirectionAwareDozerBeanMapper extends DozerBeanMapper implements DirectionAware {
private Direction direction;
public DirectionAwareDozerBeanMapper(Direction direction) {
super();
this.direction = direction;
}
public DirectionAwareDozerBeanMapper(Direction direction, List<String> mappingFiles) {
super(mappingFiles);
this.direction = direction;
}
#Override
protected Mapper getMappingProcessor() {
try {
Method m = DozerBeanMapper.class.getDeclaredMethod("initMappings");
m.setAccessible(true);
m.invoke(this);
} catch (NoSuchMethodException|SecurityException|IllegalAccessException|IllegalArgumentException|InvocationTargetException e) {
// Handle the exception as you want
}
ClassMappings arg1 = (ClassMappings)getField("customMappings");
Configuration arg2 = (Configuration)getFieldValue("globalConfiguration");
CacheManager arg3 = (CacheManager)getField("cacheManager");
StatisticsManager arg4 = (StatisticsManager)getField("statsMgr");
List<CustomConverter> arg5 = (List<CustomConverter>)getField("customConverters");
DozerEventManager arg6 = (DozerEventManager)getField("eventManager");
Map<String, CustomConverter> arg7 = (Map<String, CustomConverter>)getField("customConvertersWithId");
Mapper mapper = new DirectionAwareMappingProcessor(arg1, arg2, arg3, arg4, arg5,
arg6, getCustomFieldMapper(), arg7, direction);
return mapper;
}
private Object getField(String fieldName) {
try {
Field field = DozerBeanMapper.class.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(this);
} catch (NoSuchFieldException|SecurityException|IllegalArgumentException|IllegalAccessException e) {
// Handle the exception as you want
}
return null;
}
public Direction getDirection() {
return direction;
}
}
The class extending MappingProcessor:
public class DirectionAwareMappingProcessor extends MappingProcessor implements DirectionAware {
private Direction direction;
protected DirectionAwareMappingProcessor(ClassMappings arg1, Configuration arg2, CacheManager arg3, StatisticsManager arg4, List<CustomConverter> arg5, DozerEventManager arg6, CustomFieldMapper arg7, Map<String, CustomConverter> arg8, Direction direction) {
super(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
this.direction = direction;
}
public Direction getDirection() {
return direction;
}
}
Now, the usage.
1) Everytime you want to map the same primitive type (for example String-String), use DozerConverter with that type for both arguments as a custom converter in your dozer mappings file. The implementation of such converter should extend: DozerConverter<String,String> and implement MapperAware interface. This is important that you have MapperAware available, becuase having the mapper you will be able to cast it to DirectionAware and then get the direction.
For example:
public class MyMapper extends DozerConverter<String, String> implements MapperAware {
private DirectionAware dirAware;
public MyMapper(Class<String> cls) {
super(cls, cls);
}
#Override
public Object convert(Object existingDestinationFieldValue, Object sourceFieldValue, Class<String> destinationClass, Class<String> sourceClass) {
if (dirAware.getDirection() == Direction.FROM) {
// TODO convert sourceFieldValue for "FROM" direction and return it
} else {
// TODO convert sourceFieldValue for "TO" direction and return it
}
}
#Override
public void setMapper(Mapper mapper) {
dirAware = (DirectionAware)mapper;
}
}
2) You need to create 2 global Dozer mapper objects, one per mapping direction. They should be configured with the same mapping files, but with different direction argument. For example:
DirectionAwareDozerBeanMapper mapperFrom = DirectionAwareDozerBeanMapper(mappingFiles, Direction.FROM);
DirectionAwareDozerBeanMapper mapperTo = DirectionAwareDozerBeanMapper(mappingFiles, Direction.TO);
Of course you will need use proper mapper (from/to) to provide information to custom mappers on which direction you're mapping.
I got into same kind of issue after couple of years and somehow DozerConverter API which is a new API, still does not work properly as bi-direction !!
So, rather than getting into all these complex solutions advised here, I also created 2 one-way mapping to get over this issue(with ) . And then my conversions started working . I am using DozerConverter api like below :
public class MapToStringConverter extends DozerConverter

How to convert a Hibernate proxy to a real entity object

During a Hibernate Session, I am loading some objects and some of them are loaded as proxies due to lazy loading. It's all OK and I don't want to turn lazy loading off.
But later I need to send some of the objects (actually one object) to the GWT client via RPC. And it happens that this concrete object is a proxy. So I need to turn it into a real object. I can't find a method like "materialize" in Hibernate.
How can I turn some of the objects from proxies to reals knowing their class and ID?
At the moment the only solution I see is to evict that object from Hibernate's cache and reload it, but it is really bad for many reasons.
Here's a method I'm using.
public static <T> T initializeAndUnproxy(T entity) {
if (entity == null) {
throw new
NullPointerException("Entity passed for initialization is null");
}
Hibernate.initialize(entity);
if (entity instanceof HibernateProxy) {
entity = (T) ((HibernateProxy) entity).getHibernateLazyInitializer()
.getImplementation();
}
return entity;
}
Since Hibernate ORM 5.2.10, you can do it likee this:
Object unproxiedEntity = Hibernate.unproxy(proxy);
Before Hibernate 5.2.10. the simplest way to do that was to use the unproxy method offered by Hibernate internal PersistenceContext implementation:
Object unproxiedEntity = ((SessionImplementor) session)
.getPersistenceContext()
.unproxy(proxy);
Try to use Hibernate.getClass(obj)
I've written following code which cleans object from proxies (if they are not already initialized)
public class PersistenceUtils {
private static void cleanFromProxies(Object value, List<Object> handledObjects) {
if ((value != null) && (!isProxy(value)) && !containsTotallyEqual(handledObjects, value)) {
handledObjects.add(value);
if (value instanceof Iterable) {
for (Object item : (Iterable<?>) value) {
cleanFromProxies(item, handledObjects);
}
} else if (value.getClass().isArray()) {
for (Object item : (Object[]) value) {
cleanFromProxies(item, handledObjects);
}
}
BeanInfo beanInfo = null;
try {
beanInfo = Introspector.getBeanInfo(value.getClass());
} catch (IntrospectionException e) {
// LOGGER.warn(e.getMessage(), e);
}
if (beanInfo != null) {
for (PropertyDescriptor property : beanInfo.getPropertyDescriptors()) {
try {
if ((property.getWriteMethod() != null) && (property.getReadMethod() != null)) {
Object fieldValue = property.getReadMethod().invoke(value);
if (isProxy(fieldValue)) {
fieldValue = unproxyObject(fieldValue);
property.getWriteMethod().invoke(value, fieldValue);
}
cleanFromProxies(fieldValue, handledObjects);
}
} catch (Exception e) {
// LOGGER.warn(e.getMessage(), e);
}
}
}
}
}
public static <T> T cleanFromProxies(T value) {
T result = unproxyObject(value);
cleanFromProxies(result, new ArrayList<Object>());
return result;
}
private static boolean containsTotallyEqual(Collection<?> collection, Object value) {
if (CollectionUtils.isEmpty(collection)) {
return false;
}
for (Object object : collection) {
if (object == value) {
return true;
}
}
return false;
}
public static boolean isProxy(Object value) {
if (value == null) {
return false;
}
if ((value instanceof HibernateProxy) || (value instanceof PersistentCollection)) {
return true;
}
return false;
}
private static Object unproxyHibernateProxy(HibernateProxy hibernateProxy) {
Object result = hibernateProxy.writeReplace();
if (!(result instanceof SerializableProxy)) {
return result;
}
return null;
}
#SuppressWarnings("unchecked")
private static <T> T unproxyObject(T object) {
if (isProxy(object)) {
if (object instanceof PersistentCollection) {
PersistentCollection persistentCollection = (PersistentCollection) object;
return (T) unproxyPersistentCollection(persistentCollection);
} else if (object instanceof HibernateProxy) {
HibernateProxy hibernateProxy = (HibernateProxy) object;
return (T) unproxyHibernateProxy(hibernateProxy);
} else {
return null;
}
}
return object;
}
private static Object unproxyPersistentCollection(PersistentCollection persistentCollection) {
if (persistentCollection instanceof PersistentSet) {
return unproxyPersistentSet((Map<?, ?>) persistentCollection.getStoredSnapshot());
}
return persistentCollection.getStoredSnapshot();
}
private static <T> Set<T> unproxyPersistentSet(Map<T, ?> persistenceSet) {
return new LinkedHashSet<T>(persistenceSet.keySet());
}
}
I use this function over result of my RPC services (via aspects) and it cleans recursively all result objects from proxies (if they are not initialized).
The way I recommend with JPA 2 :
Object unproxied = entityManager.unwrap(SessionImplementor.class).getPersistenceContext().unproxy(proxy);
Starting from Hiebrnate 5.2.10 you can use Hibernate.proxy method to convert a proxy to your real entity:
MyEntity myEntity = (MyEntity) Hibernate.unproxy( proxyMyEntity );
The another workaround is to call
Hibernate.initialize(extractedObject.getSubojbectToUnproxy());
Just before closing the session.
With Spring Data JPA and Hibernate, I was using subinterfaces of JpaRepository to look up objects belonging to a type hierarchy that was mapped using the "join" strategy. Unfortunately, the queries were returning proxies of the base type instead of instances of the expected concrete types. This prevented me from casting the results to the correct types. Like you, I came here looking for an effective way to get my entites unproxied.
Vlad has the right idea for unproxying these results; Yannis provides a little more detail. Adding to their answers, here's the rest of what you might be looking for:
The following code provides an easy way to unproxy your proxied entities:
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionImplementor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaContext;
import org.springframework.stereotype.Component;
#Component
public final class JpaHibernateUtil {
private static JpaContext jpaContext;
#Autowired
JpaHibernateUtil(JpaContext jpaContext) {
JpaHibernateUtil.jpaContext = jpaContext;
}
public static <Type> Type unproxy(Type proxied, Class<Type> type) {
PersistenceContext persistenceContext =
jpaContext
.getEntityManagerByManagedType(type)
.unwrap(SessionImplementor.class)
.getPersistenceContext();
Type unproxied = (Type) persistenceContext.unproxyAndReassociate(proxied);
return unproxied;
}
}
You can pass either unproxied entites or proxied entities to the unproxy method. If they are already unproxied, they'll simply be returned. Otherwise, they'll get unproxied and returned.
Hope this helps!
Thank you for the suggested solutions! Unfortunately, none of them worked for my case: receiving a list of CLOB objects from Oracle database through JPA - Hibernate, using a native query.
All of the proposed approaches gave me either a ClassCastException or just returned java Proxy object (which deeply inside contained the desired Clob).
So my solution is the following (based on several above approaches):
Query sqlQuery = manager.createNativeQuery(queryStr);
List resultList = sqlQuery.getResultList();
for ( Object resultProxy : resultList ) {
String unproxiedClob = unproxyClob(resultProxy);
if ( unproxiedClob != null ) {
resultCollection.add(unproxiedClob);
}
}
private String unproxyClob(Object proxy) {
try {
BeanInfo beanInfo = Introspector.getBeanInfo(proxy.getClass());
for (PropertyDescriptor property : beanInfo.getPropertyDescriptors()) {
Method readMethod = property.getReadMethod();
if ( readMethod.getName().contains("getWrappedClob") ) {
Object result = readMethod.invoke(proxy);
return clobToString((Clob) result);
}
}
}
catch (InvocationTargetException | IntrospectionException | IllegalAccessException | SQLException | IOException e) {
LOG.error("Unable to unproxy CLOB value.", e);
}
return null;
}
private String clobToString(Clob data) throws SQLException, IOException {
StringBuilder sb = new StringBuilder();
Reader reader = data.getCharacterStream();
BufferedReader br = new BufferedReader(reader);
String line;
while( null != (line = br.readLine()) ) {
sb.append(line);
}
br.close();
return sb.toString();
}
Hope this will help somebody!
I found a solution to deproxy a class using standard Java and JPA API. Tested with hibernate, but does not require hibernate as a dependency and should work with all JPA providers.
Onle one requirement - its necessary to modify parent class (Address) and add a simple helper method.
General idea: add helper method to parent class which returns itself. when method called on proxy, it will forward the call to real instance and return this real instance.
Implementation is a little bit more complex, as hibernate recognizes that proxied class returns itself and still returns proxy instead of real instance. Workaround is to wrap returned instance into a simple wrapper class, which has different class type than the real instance.
In code:
class Address {
public AddressWrapper getWrappedSelf() {
return new AddressWrapper(this);
}
...
}
class AddressWrapper {
private Address wrappedAddress;
...
}
To cast Address proxy to real subclass, use following:
Address address = dao.getSomeAddress(...);
Address deproxiedAddress = address.getWrappedSelf().getWrappedAddress();
if (deproxiedAddress instanceof WorkAddress) {
WorkAddress workAddress = (WorkAddress)deproxiedAddress;
}

Categories

Resources