I'm currently trying to setup a database – using Java only. Given this simple class that might appear in the average social network app:
#Entity
class User {
#Id
private String email;
private String name;
private String otherInfo;
#ManyToMany
private List<User> contacts;
}
When the user logs in, he should receive the basic information and the list of contacts with their basic info, but not their contacts. To reduce the amount of boiler-plate code, I want to use a standard solution like Gson. However, even with lazy fetch the whole user is loaded on gson.toJson(user).
Therefore I thought of extracting the basic infos into a base class BasicUser and changing the contacts to List<BasicUser>. Now I only need to somehow circumwent the discriminator column when I fetch the contacts – of course they are all saved as complete users on the server. Unfortunately, I don't know how to archieve that. Any ideas?
If you need to get only part of the entity you can use projections. In your case it can be, for example, like this:
public interface BaseUser {
String getEmail();
String getName();
String getOtherInfo();
}
public interface UserRepo extends JpaRepository <User, String> {
List<BaseUser> findAllBy();
}
Using Jackson for serialization, the problem can be solved without writing custom serialization code. BasicUser contains the getters of the attributes, I want to serialize:
public interface BasicUser {
String getEmail();
String getFirstName();
String getLastName();
}
With a single annotation the contacts attribute is interpreted as a list of BasicUsers:
#Entity
public class User implements BasicUser {
#Id
private String email;
private String firstName;
private String lastName;
#ManyToMany
#JsonSerialize(contentAs = BasicUser.class)
private List<User> contacts = new ArrayList<>();
// ... implemented getters
}
You shouldn't have to modify your domain model just to accomodate a serialization library.
If you only want certain fields of a collection to be exposed to JSON, you could use Jackson with #JsonView (see here: How to serialize using #Jsonview with nested objects) not sure if Gson provides a similar feature as I have never used it extensively.
Related
I'm trying to build a simple application with Quarkus. Currently, I have two entity classes, which are related one-to-many:
#Entity
public class Person extends PanacheEntity {
public String name;
public LocalDate birthdate;
#OneToMany(mappedBy = "person")
public List<Address> addresses;
public static Person findByNameFirst(String name) {
return find("name", name).firstResult();
}
}
#Entity
public class Address extends PanacheEntity {
public String street;
...etc...
#ManyToOne
public Person person;
}
These are used by a simple REST webservice, which should store a Person to the database, select it again an return it:
#GET
#Path("storePerson")
#Produces(MediaType.APPLICATION_JSON)
#Transactional
public Person storePerson(
#QueryParam("name")String name,
#QueryParam("birthdate")String birthdate)
{
LocalDate birth = LocalDate.parse(birthdate, DateTimeFormatter.BASIC_ISO_DATE);
Person person = new Person(name, birth);
person.persistAndFlush();
Person p2 = Person.findByNameFirst(name);
return p2;
}
When calling the webservice the first time, the result is a JSON object with the stored data, which is as expected. When called again, an internal server error is thrown:
org.hibernate.LazyInitializationException: Unable to perform requested lazy initialization [Person.addresses] - no session and settings disallow loading outside the Session
As I understand, the error is thrown because the transaction only lasts until the storePerson method ends, but the conversion to JSON is happening outside of the method.
How can I prevent this error? I have read about the hibernate parameter "enable_lazy_load_no_trans" but it seems it is not supported in Quakus' application.properties.
The idea is to use a mapper framework such as MapStruct.
We don't recommend to directly expose your entities for 2 reasons:
the issue you have,
API management in the long run: you might have to change your model and not your API or the opposite.
There is an example here: https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-quarkus .
The Quarkus version used is a bit old but AFAICS it should still work with latest Quarkus.
You can make the error go away by using Hibernate.initialize(person.addresses), then the collection gets initialized before the transaction ends.
I have a class with a few members, and the associated setters and getters:
public class Tester implements Serializable {
#Column(name="ID", nullable=false, unique=true)
#Id
#GeneratedValue(generator="LOCATION_FACILITYTYPE_ID_GENERATOR")
#org.hibernate.annotations.GenericGenerator(name="LOCATION_FACILITYTYPE_ID_GENERATOR", strategy="native")
private int ID;
#Column(name="Value", nullable=false, unique=true, length=4)
private String value;
#Column(name="Name", nullable=false, unique=true, length=8)
private String name;
#ManyToOne(targetEntity=location.FacilityType.class, fetch=FetchType.LAZY)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.LOCK})
#JoinColumns({ #JoinColumn(name="FacilityTypeID", referencedColumnName="ID", nullable=false) })
private location.FacilityType facility;
In a JUnit, I am trying to test creating a Tester element:
Tester trythis = new Tester();
trythis.setName("Herewe");
trythis.setValue("wow1");
Tester jnode = restTemplate.postForObject(TestBase.URL + "tester/", trythis, Tester.class);
This works as expected. However, if I use code like this to include an embedded member:
FacilityType ft = new FacilityType();
ft.setValue("AL");
ft.setName("2adamlec");
Tester trythis = new Tester();
trythis.setName("Herewe");
trythis.setValue("wow1");
trythis.setFacility(ft);
Tester jnode = restTemplate.postForObject(TestBase.URL + "tester/", trythis, Tester.class);
where the embedded member with value=AL does not yet appear in the database, I still get a new row created in the Tester table ... but the value and name columns in Tester are filled with the values (AL and 2adamlec) defined for FacilityType.
Note that we are using the JPARepository framework for FacilityType and Tester. The CRUD functions are thus handled 'under the covers', and I can't debug the POST processing. I wonder if this is associated with the fact that a GET for Tester data will only return the primitive fields in the JSON reply, since there is no projection defined for FacilityType.
Am I doing something wrong to cause the FacilityType fields to be saved in lieu of the desired Tester fields in the Tester table?
The short answer: when creating the item, you have to provide the data in the same JSON format that the server expects it. If you have an embedded class, you have to create a class where the facility member is a String to house a URL, then set that member to the URL corresponding to the existing instance of the embedded class. On the receiving end, you also need a new class like this:
public class FMT_Tester_RCV {
public FMT_Tester_RCV() { }
private String value;
private String name;
private Integer id;
private Integer ormid;
private JsonNode _links;
where you can travel down the JsonNode to get the link to the embedded class instance.
I have a question about DTO class representation. I have two tables on my database, where one has two foreign keys on another one, for example:
book(id, author_name, author_age)
author(name, age, telephone)
where book author_name and author_age are foreign keys on author name and age.
Generally what's the best way to implement this situation on a DTO class?
Inside Book DTO, it's better to do something like:
public class Book {
private String id;
private Author author;
}
or something like
public class Book {
private String id;
private String author_name;
private int author_age;
}
?
DTO are objects that are being used for just transfering the values over communication channel most of time over http or https.
Note : Make your properties as public instead of private.
We should make it as simple as possible.
so use it like
public class Book {
public String id;
public String author_name;
public int author_age;
}
If you would compare the DTO without Author object vs DTO with Author object converted into json or xml, DTO without Author would be less in size.
When using Author inside Book you just add extra wrapper (for author_name and author_age) which cost you some more over communication channel.
in case if Book have multiple authors or in case of one to many relationship you need to do like:
public class Book {
public String id;
public List<Author> authors;
}
Note:But make sure Author should not have a back property with Book instance like following.
public class Author
{
public Book book;
}
in some client side framework like knockout it creates loop with observables.
I have a somewhat philosophical question relating to mapping JPA Objects to JSON Strings. Of course there is no necessity for the source object to be a persistent object - it is just that that is my situation.
I have a collection of objects that are managed by Eclipse Link. I need to turn some of these objects into JSON Strings, however the mapping is not one-to-one. I am convinced that the conversion should be loosely coupled so as to isolate the JSON objects from changes in the underlying entities.
I am planning to have the JPA entity as such:
#Entity
#Table(name = "AbnormalFlags")
public class AbnormalFlag implements java.io.Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "Code", unique = false, nullable = false)
private String code;
#Column(name = "Description", unique = false, nullable = false)
private String description;
// Getters and setters
}
and the equivalent object to be converted to JSON
public class AbnormalFlagDTO implements java.io.Serializable {
private String code;
private String description;
private Boolean disabled;
// Getters and setters
}
Is there an elegant pattern or methodology I can use to facilitate this process for several types of objects.
Thanks in anticipation
My answer: no, and also you should generally extend DTOs with care (when reusing existing DTOs). But you could use a Map<String, Object> as a DTO (if you do not use the same DTO to read the data back). Besides you could create an APT (annotation processor tool) that generates the code for DTOs from your entities and then you simply modify them.
This is a perfect use case for Blaze-Persistence Entity Views as you will most probably also want to keep an eye on the performance of the query used for fetching the data.
I created the library to allow easy mapping between JPA models and custom interface defined models. The idea is that you define your target structure the way you like and map attributes(getters) via JPQL expressions to the entity model. Since the attribute name is used as default mapping, you mostly don't need explicit mappings as 80% of the use cases is to have DTOs that are a subset of the entity model.
A mapping for your model could look as simple as the following
#EntityView(AbnormalFlag.class)
interface AbnormalFlagDTO extends Serializable {
String getCode();
String getDescription();
Boolean getDisabled();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
AbnormalFlagDTO dto = entityViewManager.find(entityManager, AbnormalFlagDTO.class, id);
The serialization of the entity view to JSON will work as expected. If you also want to deserialize objects, you will have to construct the object first and also add setters to the interface.
I am developing web application with spring mvc and Data sent to client in json format.
I want to have some views of same model object thus i can return only needed data ,not more.Jackson library #JsonIgnore , #JsonIgnoreProperties not suit this .Jackson library have also #JsonView and #JsonFilter annotations but they didnt help too.How can handle this problem.For example ,i will need possibleTarget list in some pages of UI and sometimes dont need.This is the same question but answer not help me
#Entity
public class Warrant implements Serializable {
#Column
String name;
#JsonIgnore
#ManyToOne
private User owner;
#Column
private String value;
#OneToMany(mappedBy = "warrant", targetEntity = com.endersys.lims.model.Target.class)
private List<Target> possibleTargets;
.....
}
You're mixing application layers. Don't send entities from your persistence layer to the view. Use Transfer Objects, that way you can easily control what you show the world.
You might want to use a framework like Dozer to automate data transfer between layers.