Datanucleus/mongodb: Persisting map of lists - java

I'm using datanucleus 3.2.5 / JDO for persisting objects to a MongoDB database.
While trying to persist one map of lists I'm getting the following exception:
RuntimeException: json can't serialize type [list element type here]
Some sample code:
#PersistenceCapable
public class SomeClass {
private Map<String, List<SomeOtherClass>> myAttribute;
// ...
}
#PersistenceCapable(embeddedOnly="true")
public class SomeOtherClass {
private String attribute;
// ...
}
I could get around this problem annotating the embedded attribute as #Serialized, but I would rather prefer a more elegant way.
Am I missing anything? Is there a better approach to this issue?

Quoting Andy's reply to my question in the DataNucleus forums:
No persistence spec defines any support for a container of a container. You are always recommended to make the inner container (List in your case) a field of an intermediate class.
So there are two approaches here:
Use an intermediate class
By far the most elegant and maintainable solution. Following the example toy code:
#PersistenceCapable
public class SomeClass {
private Map<String, SomeOtherClassContainer> myAttribute;
// ...
}
#PersistenceCapable(embeddedOnly="true")
public class SomeClassContainer {
private List<SomeOtherClass> myAttribute;
// ...
}
#PersistenceCapable(embeddedOnly="true")
public class SomeOtherClass {
private String attribute;
// ...
}
Mark the attribute as #Serializable
Ugly and probably a source of headaches, specially if relying on java default serialization.
#PersistenceCapable
public class SomeClass {
#Serializable
private Map<String, List<SomeOtherClass>> myAttribute;
// ...
}
#PersistenceCapable(embeddedOnly="true")
public class SomeOtherClass implements Serializable {
private String attribute;
// ...
}

Related

Inject a Map of Interface Implementations Keyed by Enum Values

I am using Java 8 with a Play framework. My goal is to inject a map whose keys are enum values and values are implementations of a specific interface.
Here is my enum:
public enum Service {
HTML("html"), TEXT("txt");
private String serviceId;
Service(String serviceId) { this.serviceId = serviceId; }
}
I have Executable interface
public interface Executable { void execute(); }
and two classes that implement it:
public class HtmlWorker implements Executable { ... }
public class TextWorker implements Executable { ... }
I would like to be able to inject Map<Service, Executable> serviceMap so I can have access to a specific implementation using a Service key:
public class Processor {
#Inject
Map<Service, Executable> serviceMap;
public void doStuff() {
Executable htmlService = this.serviceMap.get(Service.HTML);
Executable textService = this.serviceMap.get(Service.TEXT);
// do more stuff
}
}
I added bindings to the module class:
public class AppModule extends AbstractModule {
#Override
protected void configure() {
MapBinder<Service, Executable> serviceBinder = MapBinder
.newMapBinder(binder(), Service.class, Executable.class);
serviceBinder.addBinding(Service.HtmlService).to(HtmlWorker.class);
serviceBinder.addBinding(Service.TextService).to(TextWorker.class);
}
The problem is that serviceMap is never injected and it is always null inside Processor. What am I missing?
According to the official MapBinder documentation the MapBinder.addBinding should take the map's key.
As far as concerning your provided example what about changing AbstractModule's code from:
serviceBinder.addBinding(Service.HtmlService).to(HtmlWorker.class);
serviceBinder.addBinding(Service.TextService).to(TextWorker.class);
to
serviceBinder.addBinding(Service.HTML).to(HtmlWorker.class); // <-- see the enum constant here?
serviceBinder.addBinding(Service.TEXT).to(TextWorker.class);
Anyway I don't know where the class Service.HtmlService in your example comes from since you didn't state it anywhere.

How can I force spring boot to use and return the base class in MongoRepository

I have a base class like (this is an artificial example):
#Document(collection = "cars")
public class BaseCar implements Serializable
{
private int number;
private String color;
...
}
Then I have a derived class like:
#Document(collection = "cars")
public class FastCar extends BaseCar implements Serializable
{
private int numberOfWonRaces;
...
}
For both I have a MongoRepository class:
public interface BaseCarRepository extends MongoRepository<BaseCar, String> {
{ ... }
and
public interface FastCarRepository extends MongoRepository<FastCar, String> {
{ ... }
If I now save a FastCar in MongoDB the I get additionally a _class field added which indicates from where the data is coming from. In this example it shows FastCar.
In my project I have a REST API interface to get cars. I use the findBy function to get a car by its color. For example:
BaseCar baseCar = baseCarRep.findByColor(color);
Even if I use an object of BaseCar, Springboot is detecting that it is a FastCar and is returning an FastCar object with all the information.
Question:
Is there a way to force Springboot to return only a BaseCar? I do not want to send all the information to the REST API interface.
What I have done so far:
If I remove the the _class field in the MongoDB, Springboot cannot automatically detect the class anymore and is returning the BaseCar. But I do not want to lose this functionality by forcing Springboot to remove the _class (Spring data MongoDb: MappingMongoConverter remove _class)
It seems that there is also a way with projections to filter the fields which should be returned. This is to me not an elegant way as I have to write down all the fields again and I have to update it as soon as I am updating the BaseCar class.
Thank you for any help.
Philipp

Polymorphism with respect to performance

I got the following problem. I want to create SomeObject. This object consists of various nested objects NestedObject1, NestedObject2, ... I created mappers to create those nested objects Mapper1 to create NestedObject1, Mapper2 to create NestedObject2, and so on. Those Mappers call a huge amount of setters, and some of them need information from some entites from the db (and some don't). This is the problem in the java language:
public class MyClass {
#Inject
private MyDao dao;
#Inject
private Mapper1 mapper1;
#Inject
private Mapper2 mapper2;
#Inject
private Mapper3 mapper3;
#Inject
private Mapper4 mapper4;
#Inject
private Mapper5 mapper5;
public SomeObject map(Integer id) {
SomeEntity entity = dao.findById(id);
SomeObject someObject = new SomeObject();
someObject.setNestedObject1(mapper1.map(entity));
someObject.setNestedObject2(mapper2.map());
someObject.setNestedObject3(mapper3.map(entity));
someObject.setNestedObject4(mapper4.map(entity));
someObject.setNestedObject5(mapper5.map());
return someObject;
}
}
I am thinking of the following refactoring:
Make an interface Mapper and have all mappers implement this. Then I could inject the List of mappers. It would be pretty easy to add or remove on mapper, without touching MyClass. I think this is a good idea but the problem is the MyDao. Instead of one DB access I would then need 3.
The interface would then look like
public interface Mapper {
public void map(SomeObject someObject);
}
Mapper1 would look like
public class Mapper1 implements Mapper {
private static final Integer VALUTA = 1;
#Inject
private MyDao dao;
#Override
public void map(SomeObject someObject) {
SomeEntity entity = dao.findById(id); // and I have no idea what the id is
NestedObject1 nestedObject1 = new NestedObject1();
nestedObject1.setSomeField(entity.getSomething());
nestedObject1.setSomeOtherField(VALUTA);
someObject.setNestedObject1(nestedObject1);
}
}
id is unknown in this context. Include id in the signature? I have no idea...
Mapper3 and Mapper4 would have to look up the entity as well.
I was thinking about an abstract class which will look for the entity in the BeforeClass method, but I think this still get's called multiple times.
Btw: I know the title sucks, please feel free to rename it.

Need a confirmation regarding persisting a referenced (embedded) object using jdo+datanucleus

Hi I am persisting a class with a collection(List) of interface.
I see this on link
http://www.datanucleus.org/products/accessplatform_2_1/jdo/orm/embedded.html#Collection
and it says "Embedded elements cannot have inheritance (this may be allowed in the future)"
So, how to persist such objects?
I came accross the same issue a few hours ago, hope it helps others starting with jdo/datanucleus.
As stated in the current docs, the only way to persist a collection of interfaces is through an unidirectional join table. It's not possible to directly embed the objects implementing the interface.
#PersistenceCapable
public class SomeClass {
#Join
#Extension(vendorName="datanucleus", key="implementation-classes", value="ImplementingClass")
private List<SomeInterface> myList;
// this list would be embedded
private List<SomeOtherClass> myOtherList;
// ...
}
#PersistenceCapable
public interface SomeInterface {
// ...
}
#PersistenceCapable
public class ImplementingClass implements SomeInterface {
// ...
}
#PersistenceCapable(embeddedOnly="true")
public class SomeOtherClass {
// ...
}

JPA and generics

I'm wondering how an abstract class with generics would handle with JPA? I mean what kind of annotations do I need for the field?
Consider these:
#MappedSuperclass
public abstract class AbstractMyClass<T> {
// What about Strings and Integers? Do I need some kind of #LOB?
private T field;
public T getField() {
return field;
}
public void setField(T field) {
this.field = field;
}
}
And then these
#Entity
#Table(name = "String")
public class MyStringClass extends AbstractMyClass<String> {
}
#Entity
#Table(name = "Integer")
public class MyIntegerClass extends AbstractMyClass<Integer> {
}
JPA is perfectly able to handle your proposed, because the generic appears at the abstract class level and for your concrete classes it has exactly a single value per class. In fact, JPA will store your subclasses in one or more table, according to the #InheritanceStrategy you have chosen and uses different mechanism for that.
You can figure out yourself why your case is not a problem, reasoning about how an ORM could save the two classes on a DB:
You can store MyStringClass and MyIntegerClass in the same table, adding a Discriminator column so that the ORM, when it loads from the DB, know which constructor should be called.
You can store every subclass in more table.
What is not possible, on the other side, is to define a generic
#Entity
#Table(name = "MyGenericClass")
public class MyGenericClass<T> {
private T t;
public MyGenericClass(T t) {
this.t=t;
}
}
The reason for this is that, at compile time, the T is "erased" because of type erasure. It is used at compile time to verify signatures and correctness of types, but then it is turned into a java.lang.Object inside the JVM. If you follow until now, you should be able to understand the following:
In your case, every concrete subclass of AbstractMyClass has a type T which is defined for all instances of the class. While the T information is not retained into the AbstractMyClass, it is retained and unique inside the subclasses.
In the second case I posted, each possible concrete instance of MyGenericClass could have a possible different value for T, and because of type erasure this information is not retained.
*Note: the fact that the second case cannot be handled by JPA is absolutely reasonable and if you fall in that case you should ask yourself questions about your design. Generics are a great tool to design flexible classes which can handle other classes in a type-safe manner, but type-safe is a programming language concept which has nothing to do with persistance.
Extra : you could use javap to see what really is erasure. Take off annotations from MyGenericClass and compile it.
G:\>javac MyGenericClass.java
G:\>javap -p MyGenericClass
Compiled from "MyGenericClass.java"
public class MyGenericClass extends java.lang.Object{
private java.lang.Object t;
public MyGenericClass(java.lang.Object);
}
We can. if the T implements Serializable
#Entity
public class IgsSubject extends BasicObject implements Serializable{
private static final long serialVersionUID = -5387429446192609471L;
#MappedSuperclass
public class IgsBasicLog<T> extends BasicObject {
#ManyToOne
#JoinColumn(name = "ITEM_ID")
private T item;
#Entity
public class IgsLogA extends IgsBasicLog<IgsSubject> implements Serializable {
private static final long serialVersionUID = -8207430344929724212L;
}

Categories

Resources