Complex object mapping in Android - java

I'm dabbling in the Android Room persistence library, and I have this object that is a result of a JSON parse:
#Entity
public class Cluster {
#PrimaryKey
private int id;
private boolean is_up;
private ClusterStatus status;
private ClusterLoad load;
private List<Server> servers = null;
private UserQueries user_queries;
}
Example of a ClusterStatus Object (getters and setters omitted for brevity), to show that TypeConverter isn't helpful:
public class ClusterStatus {
private boolean isOnline;
private String name;
private int upTime;
}
Do I map all these inner Objects as separate Entities?
How do I map a List of Objects properly? (I've read the Doc but didn't get why the annotations go the way they go)
Maybe you can advise me some other ORM, that handles these relations better?

Use TypeConverters
Make a TypeConverter class like this :
(This example is for Date, you can similarly make for other Objects)
public class DateConverter {
#TypeConverter
public static Date toDate(Long timestamp) {
return timestamp == null ? null : new Date(timestamp);
}
#TypeConverter
public static Long toTimestamp(Date date) {
return date == null ? null : date.getTime();
}
}
Add TypeConverter in RoomDatabase class like this:
#Database(entities = {TaskEntry.class}, version = 1, exportSchema = false)
#TypeConverters(DateConverter.class)
public abstract class AppDatabase extends RoomDatabase
{
...
...
}
Official Documentation :
TypeConverter
TypeConverters

Related

Issues with Android Studio Room Type Converter

For some reason, I am having issues with the typeconverter in Android Studios. I have tried a variety of solutions from other posts, but continue to get this error. When I tried Arraylist of strings, instead of ingredients, the typeconverter does work.
error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
#Entity(tableName = "Recipes")
public class recipesDB {
#PrimaryKey
#NonNull
#ColumnInfo(name = "recipeID")
private int id;
//Foreign Key to be used from Ingredients
#ColumnInfo(name = "name")
private String name;
#ColumnInfo(name = "url")
private String url;
#ColumnInfo(name = "readyInMinutes")
private int readyInMinutes;
//Issue Here
#ColumnInfo(name = "ingredients")
private ArrayList<ingredient> ingredients;
DataConverter class:
public class DataConverter{
#TypeConverter
public static ArrayList<ingredient> toIngredient(String s){
if(s == null){
return null;
}
Gson gson = new Gson();
Type listType = new TypeToken<ArrayList<ingredient>>(){}.getType();
ArrayList<ingredient> ingredients = gson.fromJson(s, listType);
return ingredients;
}
#TypeConverter
public static String getIngredients(ArrayList<ingredient> ingredients){
if(ingredients == null){
return null;
}
Gson gson = new Gson();
Type type = new TypeToken<ArrayList<ingredient>>() {}.getType();
String json = gson.toJson(ingredients);
return json;
}
EDIT
AppDatabase:
#Database(entities = {recipesDB.class}, version = 1, exportSchema = false)
#TypeConverters({DataConverter.class})
public abstract class AppDatabase extends RoomDatabase {
private static AppDatabase INSTANCE;
public abstract recipesDAO recipesdao();
//public abstract pantryDAO pantrydao();
public static AppDatabase getInMemoryDatabase(Context context) {
if (INSTANCE == null) {
INSTANCE = Room.inMemoryDatabaseBuilder(context.getApplicationContext(), AppDatabase.class).build();
}
return INSTANCE;
}
public static void destroyInstance(){INSTANCE = null;}
}
So the reason for the issue was due to a duplicate ingredient class outside the scope of my current folder directory. By changing the class ingredient to Ingredient, it must have cleared up the confusion for Room.
NEW ANSWER Turns out that renaming ingredient to Ingredient solved the problem (??) so… maybe keep an eye on those Class names and follow the Java convention of ClassNamesStartWithUpperCase.
OLD ANSWER:
I think, you may need to create a separate “entity” for your ingredient (just like recipesDB is).
Then ingredient sets up a #ForeignKey back to recipesDB.
And finally, you can add a POJO to have both recipesDB and ArrayList<Ingredient>.
I don’t have a sample here with me, but I’ve seen a bunch online.
I think the problem is that your “Converter” doesn’t know how to convert an “Ingredient”. It knows how to convert a List<Ingredient> though, but it’s not the same thing. ;)

Java JPA Preventing Proxies from calling db

I have a spring boot (1.5.4.RELEASE) project using Java 8. I have an entity and it's related domain class like this:
#Entity
#Table(name = "Foo", schema = "dbo")
public class FooEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Id")
private int id;
#Column(name="Name")
private String name;
#Column(name="Type")
private String type;
#Column(name="Color")
private String color;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "Car")
private Car car;
//getter and setter
}
public class Foo {
private int id;
private String name;
private String type;
private String color;
private Car car;
//Constructors and getters
}
I want to create a repository that fetches this Foo object from the DB but only fetching the complex fields if the user asks for them to prevent unnecessary join statements. The repo looks like this:
import static com.test.entities.QFooEntity.fooEntity;
import static com.test.entities.QCarEntity.carEntity;
#Repository
public class FooRepository {
private final JPAQuery<FooEntity> query = createQuery().from(fooEntity);
public FooRepository getFooByName(String name) {
query.where(fooEntity.name.eq(name));
return this;
}
public FooRepository withCar() {
query.leftJoin(fooEntity.car, carEntity).fetchJoin();
return this;
}
public Foo fetch() {
FooEntity entity = query.fetchOne();
return FooMapper.mapEntityToDomain().apply(entity);
}
}
So a barebones call for a Foo object will return the Entity with values for all the fields except for the car field. If the user wants car information then they have to explicitly call withCar.
Here is the mapper:
public class FooMapper {
public static Function<FooEntity, Foo> mapEntityToDomain() {
return entity -> {
return new Foo(e.getId(), e.getName(), e.getType(), e.getColor(), e.getCar());
};
}
}
The problem is when you do e.getCar() if the value is not there (i.e. there's a proxy present) JPA will go out and fetch it for you. I don't want this to be the case. It will just grab the values and map them to the domain equivalent if it's not there then null.
One solution that I've heard (and tried) is calling em.detach(entity); however, this doesn't work as I intended because it throws an exception when you try to access getCar and I've also heard this is not best practice.
So my question is what is the best way to create a repo using a builder pattern on a JPA entity and not have it call the DB when trying to map.
You could create a utility method that will return null if the given object is a proxy and is not initialized:
public static <T> T nullIfNotInitialized(T entity) {
return Hibernate.isInitialized(entity) ? entity : null;
}
Then you can call the method wherever you need it:
return new Foo(e.getId(), e.getName(), e.getType(), e.getColor(), nullIfNotInitialized(e.getCar()));
Just map it to a new object and leave out the Car relation, this is the standard approach. You can use MapStruct and just ignore the car field during mapping: http://mapstruct.org/documentation/stable/reference/html/#inverse-mappings
Just don't map the car... Map a field holding the ID and use another method to get the actual Car. I would use a distinctive method name, to differentiate it from the other getters.
class FooEntity {
#Column
private int carId;
public int getCarId() {
return carId;
}
public void setCarId(int id) {
this.carId = id;
}
public Car fetchCar(CarRepository repo) {
return repo.findById(carId);
}
}
You can write query on top of JPA
#Query("select u from Car c")
import org.springframework.data.repository.CrudRepository;
import com.example.model.FluentEntity;
public interface DatabaseEntityRepository extends CrudRepository<FooEntity , int > {
}
As you said
I don't want this to be the case. It will just grab the values and map them to the domain equivalent, if it's not there then null.
Then you just set it to null, because the field car will always not be there.
Otherwise, if you mean not there is that the car not exists in db, for sure a subquery(call the proxy) should be made.
If you want to grab the car when call Foo.getCar().
class Car {
}
class FooEntity {
private Car car;//when call getCar() it will call the proxy.
public Car getCar() {
return car;
}
}
class Foo {
private java.util.function.Supplier<Car> carSupplier;
public void setCar(java.util.function.Supplier<Car> carSupplier) {
this.carSupplier = carSupplier;
}
public Car getCar() {
return carSupplier.get();
}
}
class FooMapper {
public static Function<FooEntity, Foo> mapEntityToDomain() {
return (FooEntity e) -> {
Foo foo = new Foo();
foo.setCar(e::getCar);
return foo;
};
}
}
Make sure you have the db session ,when you call Foo.getCar()
You could try adding state to your repository and influence the mapper. Something like this:
import static com.test.entities.QFooEntity.fooEntity;
import static com.test.entities.QCarEntity.carEntity;
#Repository
public class FooRepository {
private final JPAQuery<FooEntity> query = createQuery().from(fooEntity);
private boolean withCar = false;
public FooRepository getFooByName(String name) {
query.where(fooEntity.name.eq(name));
return this;
}
public FooRepository withCar() {
query.leftJoin(fooEntity.car, carEntity).fetchJoin();
withCar = true;
return this;
}
public Foo fetch() {
FooEntity entity = query.fetchOne();
return FooMapper.mapEntityToDomain(withCar).apply(entity);
}
}
In your mapper, you then include a switch to enable or disable car lookups:
public class FooMapper {
public static Function<FooEntity, Foo> mapEntityToDomain(boolean withCar) {
return e -> {
return new Foo(e.getId(), e.getName(), e.getType(), e.getColor(), withCar ? e.getCar() : null);
};
}
}
If you then use new FooRepository().getFooByName("example").fetch() without the withCar() call, e.getCar() should not be evaluated inside FooMapper
You may want to use the PersistentUnitUtil class to query if an attribute of entity object is already loaded or not. Based on that you may skip the call to corresponding getter as shown below. JpaContext you need to supply to user entity bean mapper.
public class FooMapper {
public Function<FooEntity, Foo> mapEntityToDomain(JpaContext context) {
PersistenceUnitUtil putil = obtainPersistentUtilFor(context, FooEntity.class);
return e -> {
return new Foo(
e.getId(),
e.getName(),
e.getType(),
e.getColor(),
putil.isLoaded(e, "car") ? e.getCar() : null);
};
}
private PersistenceUnitUtil obtainPersistentUtilFor(JpaContext context, Class<?> entity) {
return context.getEntityManagerByManagedType(entity)
.getEntityManagerFactory()
.getPersistenceUnitUtil();
}
}

Java EE - [class <ClassName] uses a non-entity [class <ClassName>] as target entity in the relationship attribute

I'm developing a Java EE application with persistence.
My Car class has a few Reservation and the Reservation class extends the Quote class.
For some reason Reservation is not an Entity class. My guess is that there is something wrong with the inheritance but I can't seem to figure it out.
Car looks something like this:
#Entity
public class Car {
#Id
private int id;
#OneToOne(cascade=PERSIST, mappedBy="CarType")
private CarType type;
#OneToMany(cascade=REMOVE, mappedBy="Quote")
private Set<Reservation> reservations;
public Car() {}
.
.
.
public boolean equals(Object otherObject) {
...
}
public int hashCode() {
...
}
Reservation looks something like this
#Entity
#Table(name = "Reservation")
public class Reservation extends Quote {
#Id
#GeneratedValue(strategy=AUTO)
#Column(name="reservationId")
private int reservationId;
private int carId;
public Reservation() {}
public Reservation(Quote quote, int carId) {
super(quote.getCarRenter(), quote.getStartDate(), quote.getEndDate(),
quote.getRentalCompany(), quote.getCarType(), quote.getRentalPrice());
this.carId = carId;
}
.
.
.
public boolean equals(Object otherObject) {
...
}
public int hashCode() {
...
}
}
Quote looks something like this:
#MappedSuperclass
public class Quote implements Serializable {
#Id
#GeneratedValue(strategy=AUTO)
#Column(name="quoteId")
private int quoteId;
#Temporal(DATE)
private Date startDate;
#Temporal(DATE)
private Date endDate;
private String carRenter;
private String rentalCompany;
private String carType;
private double rentalPrice;
public Quote() {}
public Quote(String carRenter, Date start, Date end, String rentalCompany, String carType, double rentalPrice) {
...
}
.
.
.
#Override
public int hashCode() {
...
}
#Override
public boolean equals(Object obj) {
...
}
}
Why is Reservation not a correct Entity class?
The entity Reservation must delete the #id because daughter inherits it from super class
Designates a class whose mapping information is applied to the entities that inherit from it. A mapped superclass has no separate table defined for it.
I solved the problem by doing the following things:
I removed the id from Reservation because it inherits it from Quote
I manually added Reservation to persistence.xml as an Entity

Specify field is transient for MongoDB but not for RestController

I'm using spring-boot to provide a REST interface persisted with MongoDB. I'm using the 'standard' dependencies to power it, including spring-boot-starter-data-mongodb and spring-boot-starter-web.
However, in some of my classes I have fields that I annotate #Transient so that MongoDB does not persist that information. However, this information I DO want sent out in my rest services. Unfortunately, both MongoDB and the rest controller seem to share that annotation. So when my front-end receives the JSON object, those fields are not instantiated (but still declared). Removing the annotation allows the fields to come through in the JSON object.
How I do configure what is transient for MongoDB and REST separately?
Here is my class
package com.clashalytics.domain.building;
import com.clashalytics.domain.building.constants.BuildingConstants;
import com.clashalytics.domain.building.constants.BuildingType;
import com.google.common.base.Objects;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import java.util.*;
public class Building {
#Id
private int id;
private BuildingType buildingType;
private int level;
private Location location;
// TODO http://stackoverflow.com/questions/30970717/specify-field-is-transient-for-mongodb-but-not-for-restcontroller
#Transient
private int hp;
#Transient
private BuildingDefense defenses;
private static Map<Building,Building> buildings = new HashMap<>();
public Building(){}
public Building(BuildingType buildingType, int level){
this.buildingType = buildingType;
this.level = level;
if(BuildingConstants.hpMap.containsKey(buildingType))
this.hp = BuildingConstants.hpMap.get(buildingType).get(level - 1);
this.defenses = BuildingDefense.get(buildingType, level);
}
public static Building get(BuildingType townHall, int level) {
Building newCandidate = new Building(townHall,level);
if (buildings.containsKey(newCandidate)){
return buildings.get(newCandidate);
}
buildings.put(newCandidate,newCandidate);
return newCandidate;
}
public int getId() {
return id;
}
public String getName(){
return buildingType.getName();
}
public BuildingType getBuildingType() {
return buildingType;
}
public int getHp() {
return hp;
}
public int getLevel() {
return level;
}
public Location getLocation() {
return location;
}
public void setLocation(Location location) {
this.location = location;
}
public BuildingDefense getDefenses() {
return defenses;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Building building = (Building) o;
return Objects.equal(id, building.id) &&
Objects.equal(hp, building.hp) &&
Objects.equal(level, building.level) &&
Objects.equal(buildingType, building.buildingType) &&
Objects.equal(defenses, building.defenses) &&
Objects.equal(location, building.location);
}
#Override
public int hashCode() {
return Objects.hashCode(id, buildingType, hp, level, defenses, location);
}
}
As is, hp and defenses show up as 0 and null respectively. If I remove the #Transient tag it comes through.
As long as you use org.springframework.data.annotation.Transient it should work as expected. Jackson knows nothing about spring-data and it ignores it's annotations.
Sample code, that works:
interface PersonRepository extends CrudRepository<Person, String> {}
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.mongodb.core.mapping.Document;
#Document
class Person {
#Id
private String id;
private String name;
#Transient
private Integer age;
// setters & getters & toString()
}
#RestController
#RequestMapping("/person")
class PersonController {
private static final Logger LOG = LoggerFactory.getLogger(PersonController.class);
private final PersonRepository personRepository;
#Autowired
PersonController(PersonRepository personRepository) {
this.personRepository = personRepository;
}
#RequestMapping(method = RequestMethod.POST)
public void post(#RequestBody Person person) {
// logging to show that json deserialization works
LOG.info("Saving person: {}", person);
personRepository.save(person);
}
#RequestMapping(method = RequestMethod.GET)
public Iterable<Person> list() {
Iterable<Person> list = personRepository.findAll();
// setting age to show that json serialization works
list.forEach(foobar -> foobar.setAge(18));
return list;
}
}
Executing POST http://localhost:8080/person:
{
"name":"John Doe",
"age": 40
}
Log output Saving person: Person{age=40, id='null', name='John Doe'}
Entry in person collection:
{ "_id" : ObjectId("55886dae5ca42c52f22a9af3"), "_class" : "demo.Person", "name" : "John Doe" } - age is not persisted
Executing GET http://localhost:8080/person:
Result: [{"id":"55886dae5ca42c52f22a9af3","name":"John Doe","age":18}]
I solved by using #JsonSerialize. Optionally you can also opt for #JsonDeserialize if you want this to be deserailized as well.
#Entity
public class Article {
#Column(name = "title")
private String title;
#Transient
#JsonSerialize
#JsonDeserialize
private Boolean testing;
}
// No annotations needed here
public Boolean getTesting() {
return testing;
}
public void setTesting(Boolean testing) {
this.testing = testing;
}
The problem for you seems to be that both mongo and jackson are behaving as expected. Mongo does not persist the data and jackson ignores the property since it is marked as transient. I managed to get this working by 'tricking' jackson to ignore the transient field and then annotating the getter method with #JsonProperty. Here is my sample bean.
#Entity
public class User {
#Id
private Integer id;
#Column
private String username;
#JsonIgnore
#Transient
private String password;
#JsonProperty("password")
public String getPassword() {
return // your logic here;
}
}
This is more of a work around than a proper solution so I am not sure if this will introduce any side effects for you.
carlos-bribiescas,
what version are you using for it. It could be version issue. Because this transient annotation is meant only for not persisting to the mongo db. Please try to change the version.Probably similar to Maciej one (1.2.4 release)
There was issue with json parsing for spring data project in one of the version.
http://www.widecodes.com/CyVjgkqPXX/fields-with-jsonproperty-are-ignored-on-deserialization-in-spring-boot-spring-data-rest.html
Since you are not exposing your MongoRepositories as restful endpoint with Spring Data REST it makes more sense to have your Resources/endpoint responses decoupled from your domain model, that way your domain model could evolve without impacting your rest clients/consumers. For the Resource you could consider leveraging what Spring HATEOAS has to offer.
I solved this question by implementing custom JacksonAnnotationIntrospector:
#Bean
#Primary
ObjectMapper objectMapper() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
AnnotationIntrospector annotationIntrospector = new JacksonAnnotationIntrospector() {
#Override
protected boolean _isIgnorable(Annotated a) {
boolean ignorable = super._isIgnorable(a);
if (ignorable) {
Transient aTransient = a.getAnnotation(Transient.class);
JsonIgnore jsonIgnore = a.getAnnotation(JsonIgnore.class);
return aTransient == null || jsonIgnore != null && jsonIgnore.value();
}
return false;
}
};
builder.annotationIntrospector(annotationIntrospector);
return builder.build();
}
This code makes invisible org.springframework.data.annotation.Transient annotation for Jackson but it works for mongodb.
You can use the annotation org.bson.codecs.pojo.annotations.BsonIgnore instead of #transient at the fields, that MongoDB shall not persist.
#BsonIgnore
private BuildingDefense defenses;
It also works on getters.

JPA 2.1 Attribute Converter convert enum still insert int

i'm using spring data jpa with hibernate as provider.
i'm trying to persist my enums on varchar(enum.tostring) instead of (0,1,2)
my enum class:
public enum MagasinType {
PRINCIPAL {
#Override
public String toString() {
return "Principale".toUpperCase();
}
},
SECONDARY {
#Override
public String toString() {
return "Secondaire".toUpperCase();
}
},
MOBILE {
#Override
public String toString() {
return "Mobile".toUpperCase();
}
};
public abstract String toString();
}
my converter
#Converter(autoApply = true)
public class MagasinConverter implements attributeConverter <MagasinType,String>{
#Override
public String convertToDatabaseColumn(MagasinType magasinType) {
switch (magasinType){
case MOBILE:return "MOBILE";
case PRINCIPAL:return "PRINCIPAL";
case SECONDARY:return "SECONDARY";
default:throw new IllegalArgumentException("valeur incorrecte" + magasinType);
}
}
#Override
public MagasinType convertToEntityAttribute(String s) {
switch (s){
case "MOBILE": return MagasinType.MOBILE;
case "SECONDARY": return MagasinType.SECONDARY;
case "PRINCIPAL": return MagasinType.PRINCIPAL;
default:throw new IllegalArgumentException("valeur incorrecte" + s);
}}}
my entity
#Entity
#Table(name = "MAGASIN")
public class Magasin extends AbstractEntity {
#Column(name = "LIBELLE", nullable = false)
private String libelle;
#Column(name = "DESCR")
private String descr;
#Convert(converter = MagasinConverter.class)
private MagasinType type;
#Column(name = "LOCATION")
private String localisation;
#Version
private long version;
//getters setters omitted
}
my java config : https://gist.github.com/anonymous/480ef7a58cdcc50e7481
my app.properties : https://gist.github.com/anonymous/685eaca98fcba9c33872
and finally my test method : https://gist.github.com/anonymous/8bb60fee39a201558e19
please help me on it, i want to use #convert new jpa2.1 feature instead of
#enumerated
i tried to put the annotation on the getter and it works.
now i can call the #convert to convert enums to strings and visversa when pulling from database.
the same problem happened when i added #manytoOne on my class attribute, i got a weired problem, no column was added to the table entity.
but when i annotated the getter. every thing was ok.
please take a look at my github repo to further infos
https://github.com/zirconias/RFID_REWRITE

Categories

Resources