I'm using Play! 2.1.4 framework with Ebean/MySQL.
The system basically keeps track of courses, and their requirements. Eg, if you want to take Advanced Art, you must first take regular Art. This is a ManyToMany Relationship, as I understand, but I could be wrong.
Here's my model:
#Entity
public class Course {
public static Model.Finder<Long,Course> find = new Model.Finder<Long,Course>(Long.class, Course.class);
#Id
private Long id;
private String name;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable( name = "course_course",
joinColumns = #JoinColumn(name = "course_id1"),
inverseJoinColumns = #JoinColumn(name = "course_id2"))
private Set<Course> courseRequirements = new HashSet<Course>();
private String availability;
private Long category;
#Lob
private String description;
#Lob
private String otherRequirements;
private Long bucketId;
// Getters and setters...
Here's how I return the JSON to my frontend:
List<Course> courses = Ebean.find(Course.class).fetch("courseRequirements").findList();
JsonContext jsonContext = Ebean.createJsonContext();
return ok(jsonContext.toJsonString(courses));
However, in the "courseRequirements" key in the JSON, it opens up a new array with full course objects in it. I just want to get an array of the course IDs it requires, for example:
courseRequirements: [3, 5]
means that: this course requires you to take courses with ID 3 and 5 first.
I rewrote my getter to this:
public Set getCourseRequirements() {
Set requiredCourseIds = new HashSet();
for(Course course : courseRequirements)
{
requiredCourseIds.add(course.getId());
}
return requiredCourseIds;
}
because I thought that Ebean will pull that when trying to fill in the JSON key, but that's not the case.
How can I change it so that it only returns an array of course IDs for the courseRequirements key, instead of full objects?
Thanks!
Instead of toJsonString(Object o) you should use toJsonString(Object o, boolean pretty, JsonWriteOptions options).
With JsonWriteOptions "You can explicitly state which properties to include in the JSON output for the root level and each path."
http://www.avaje.org/static/javadoc/pub/com/avaje/ebean/text/json/JsonWriteOptions.html
Related
I have a user that can have a wallet. Now, when user create a wallet I want to give him a option to create a transaction on that wallet. So, on that form I would like to have next fields, so prompt user to insert:
Amount of transaction, Date, note of transaction, category of transaction
So far I have this in Spring:
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "transaction_id")
private Integer id;
private double amount;
private String note;
#DateTimeFormat(pattern = "dd-MM-yyyy")
#Column(name = "date")
private Date date;
But I have problem with field category. I want to prompt user to pick from dropdown menu one of category from the list. But how to create a field categories that will be filled with names of categories?
I tried with:
#Column(name = "categories")
private List categories;
But I'm getting:
Could not determine type for: java.util.List, at table: transaction, for columns: [org.hibernate.mapping.Column(categories)]
Spring JPA lets you handle this a couple of different ways. Since you didn't specify what type of thing a category is, I am assuming you want it to be a String.
#ElementCollection
The first, easiest, and generally recommended way is to use the #ElementCollection. If your categories are fairly well fixed, you can even use enums for this.
#ElementCollection
private List<String> categories=new ArrayList<>();
With this, JPA will generate a second table in the database called transaction_categories You don't have to create an Entity for this table or anything. Everything is handled under the covers.
JsonB
If you are using postgres 10+ (I think) you can make a column into a JSON object. You will need the following dependency in your gradle.
implementation 'com.vladmihalcea:hibernate-types-52:2.15.1'
And you will need to change your model thus:
#TypeDefs({
#TypeDef(name = "json", typeClass = JsonType.class)
})
#Entity
public class Transaction {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "transaction_id")
private Integer id;
private double amount;
private String note;
#DateTimeFormat(pattern = "dd-MM-yyyy")
#Column(name = "date")
private Date date;
#Type(type = "json")
#Column(columnDefinition = "jsonb")
private List<String> categories=new ArrayList<>();
}
So long as this list does not become gigantic, it is a pretty decent solution.
Hello i am new to JPA and i have created an application where i want to find all the users posts that where made from the start of the month to the last day of the month.My model only contains the date the post has been created at as you can see below.I want to retrieve all the posts that where created in that specific month.I have a method below where i am using LocalDate(threetenbp dependency) to provide that(not sure if this is correct).
#Entity
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private LocalDate createdAt;
private LocalDate modifiedAt;
#NotBlank(message = "Post Title is required")
private String title;
#Basic(fetch = FetchType.LAZY)
private String image;
#NotBlank(message = "Post Content is required")
private String content;
#OneToMany(cascade = CascadeType.ALL)
private List<Comment> comments = new ArrayList<>();
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "POST_TAG", joinColumns = {#JoinColumn(name = "post_id")},
inverseJoinColumns = {#JoinColumn(name = "tag_id")})
private Set<Tag> tags = new HashSet<>();```
public List<Post> doSomething() {
// something that should execute on 1st day every month # 00:00
LocalDate now = LocalDate.now();
LocalDate start = now.withDayOfMonth(1);
LocalDate end = now.withDayOfMonth(now.lengthOfMonth());
List<Post> postsThisMonth = postRepository.findAllBetweenCreatedAtandend(start, end);
return postsThisMonth;
}
As you can see in this method i am providing the current date, and the first day of the month and the last day of the month and then i call the repository with those values.
#Repository
public interface PostRepository extends CrudRepository<Post, Long> {
List<Post> findAllBetweenCreatedAtandend(LocalDate start, LocalDate end);
}
Problem is the repository method is not working.I am getting errors like
(org.threeten.bp.LocalDate,org.threeten.bp.LocalDate)! No property No property findAllBetweenCreatedAtandend found for type Post!
Please help me create a working method for my repository that returns the posts between the 2 dates.
Thank you !
The correct method name would be findAllByCreatedAtBetween
Also, I'm not sure what 'LocalDate(threetenbp dependency)' is, but JPA only works with parameter types it understands how to map to database types. Adding support for custom types as query parameters is a pain in JPA, and I'm not even entirely sure it's possible with Spring Data on top.
the problem is that i won't have the methods provided here LocalDate start = now.withDayOfMonth(1); LocalDate end = now.withDayOfMonth(now.lengthOfMonth());
You can use TemporalAdjusters.firstDayOfMonth() and TemporalAdjusters.lastDayOfMonth()
I have One-To-Many relationship, here is my code
#Entity
#Table(name = "catalog")
public class Catalog {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "catalog_id")
private int catalog_id;
#NotEmpty
#Size(min = 3, max = 255)
#Column(name = "name", nullable = false)
private String name;
#OneToMany(mappedBy="mycatalogorder")
private List<Order> orders;
#OneToMany(mappedBy="mycatalog")
private List<CatalogItem> items;
// setters and getters
}
#Entity
#Table(name = "catalogitem")
public class CatalogItem {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "catalogitem_id")
private int catalogitem_id;
#NotEmpty
#Size(min = 3, max = 255)
#Column(name = "name", nullable = false)
private String name;
#NotEmpty
#Column(name = "price", nullable = false)
private Double price;
#OneToOne(mappedBy="ordercatalogitem", cascade=CascadeType.ALL)
private OrderItem morderitem;
#ManyToOne
#JoinColumn(name="catalog_id", nullable=false)
private Catalog mycatalog;
// setters and getters
}
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "order_id")
private int order_id;
#NotEmpty
#Size(min = 3, max = 255)
#Column(name = "name", nullable = false)
private String name;
#NotEmpty
#Size(min = 3, max = 1024)
#Column(name = "note", nullable = false)
private String note;
#Temporal(TemporalType.TIMESTAMP)
#DateTimeFormat(pattern = "ddmmYYYY HH:mm:ss")
#Column(name = "created", nullable = false)
private Date created;
#OneToMany(mappedBy="myorder")
private Set<OrderItem> orderItems;
#ManyToOne
#JoinColumn(name="catalog_id", nullable=false)
private Catalog mycatalogorder;
#PrePersist
protected void onCreate() {
created = new Date();
}
// setters and getters
}
#Entity
#Table(name = "orderitem")
public class OrderItem {
#Id
#Column(name="catalogitem_id", unique=true, nullable=false)
#GeneratedValue(generator="gen")
#GenericGenerator(name="gen", strategy="foreign", parameters=#Parameter(name="property", value="catalogitem"))
private int catalogitem_id;
#Column(name = "quantity")
private int quantity;
#OneToOne
#PrimaryKeyJoinColumn
private CatalogItem ordercatalogitem;
#ManyToOne
#JoinColumn(name="order_id", nullable=false)
private Order myorder;
// setters and getters
}
And I am getting the exception:
org.springframework.http.converter.HttpMessageNotWritableException:
Could not write content: failed to lazily initialize a collection of
role: com.example.helios.model.Catalog.items, could not initialize
proxy - no Session; nested exception is
com.fasterxml.jackson.databind.JsonMappingException: failed to lazily
initialize a collection of role:
com.example.helios.model.Catalog.items, could not initialize proxy -
no Session
org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:271)
org.springframework.http.converter.AbstractGenericHttpMessageConverter.write(AbstractGenericHttpMessageConverter.java:100)
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:222)
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.handleReturnValue(HttpEntityMethodProcessor.java:183)
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:80)
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:126)
My versions is:
SpringFramework 4.2.4.RELEASE
Hibernate 4.3.11.Final
Jackson 2.7.4
Jacksontype 2.7.1
This is the normal Hibernate behaviour
In one to many relations, hibernate loads the father entity (Catalog in your case) but it will load the children entities List (List items and List orders in your case) in a LAZY mode
This means you can't access to these objects because they are just proxies and not real objects
This is usefull in order to avoid to load the full DB when you execute a query
You have 2 solution:
Load children entities in EAGER mode (I strongly suggest to you to not do it because you can load the full DB.... but it is something related to your scenario
You don't serialize in your JSON the children entities by using the com.fasterxml.jackson.annotation.JsonIgnore property
Angelo
A third option which can be useful if you don't want to use EAGER mode and load up everything is to use Hibernate::initialize and only load what you need.
Session session = sessionFactory.openSession();
Catalog catalog = (Catalog) session.load(Catalog.class, catalogId);
Hibernate.initialize(shelf);
More information
I had the same problem but a fixed by:
#OneToMany
#JoinColumn(name = "assigned_ingredient", referencedColumnName = "ingredient_id")
#Fetch(FetchMode.JOIN) // Changing the fetch profile you can solve the problem
#Where(clause = "active_ind = 'Y'")
#OrderBy(clause = "meal_id ASC")
private List<Well> ingredients;
you can have more information here: https://vladmihalcea.com/the-best-way-to-handle-the-lazyinitializationexception/
It's caused by an infinite loop when parsing datas to JSON.
You can solve this by using #JsonManagedReference and #JsonBackReference annotations.
Definitions from API :
JsonManagedReference (https://fasterxml.github.io/jackson-annotations/javadoc/2.5/com/fasterxml/jackson/annotation/JsonManagedReference.html) :
Annotation used to indicate that annotated property is part of two-way
linkage between fields; and that its role is "parent" (or "forward")
link. Value type (class) of property must have a single compatible
property annotated with JsonBackReference. Linkage is handled such
that the property annotated with this annotation is handled normally
(serialized normally, no special handling for deserialization); it is
the matching back reference that requires special handling
JsonBackReference: (https://fasterxml.github.io/jackson-annotations/javadoc/2.5/com/fasterxml/jackson/annotation/JsonBackReference.html):
Annotation used to indicate that associated property is part of
two-way linkage between fields; and that its role is "child" (or
"back") link. Value type of the property must be a bean: it can not be
a Collection, Map, Array or enumeration. Linkage is handled such that
the property annotated with this annotation is not serialized; and
during deserialization, its value is set to instance that has the
"managed" (forward) link.
Example:
Owner.java:
#JsonManagedReference
#OneToMany(mappedBy = "owner", fetch = FetchType.EAGER)
Set<Car> cars;
Car.java:
#JsonBackReference
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "owner_id")
private Owner owner;
Another solution is to use #JsonIgnore which will just set null to the field.
Here is my solution for this task with Hibernate. I marked hibernate releation with #JsonIgnore and use custom field for jackson, in which I check if the field is loaded. If you need serialize collection to json then you should manualy call collection getter during hibernate transaciton.
#JsonIgnore
#OneToMany(mappedBy = "myorder")
private List<OrderItem> orderItems = new ArrayList<>();
#JsonProperty(value = "order_items", access = JsonProperty.Access.READ_ONLY)
private List<OrderItem> getOrderItemsList() {
if(Hibernate.isInitialized(this.relatedDictionary)){
return this.relatedDictionary;
} else{
return new ArrayList<>();
}
}
#JsonProperty(value = "order_items", access = JsonProperty.Access.WRITE_ONLY)
private void setOrderItemsList(List<OrderItem> orderItems) {
this.orderItems = orderItems;
}
I know this is an old post but this might still help someone facing a similar issue. To solve the problem, iterate through the list of items and set the lazy-loadable collection to null. Then set your mapper to include NON-NULL
for (Catalog c : allCatalogs) {
c.setItems(null);
}
objectMapper.setSerializationInclusion(Include.NON_NULL)
Using FetchType.LAZY , if still getting the error "Could not write content: failed to lazily initialize a collection of role" , that may be probably caused by somewhere in the logic (perhaps in a controller) , Catalog is being tried to be deserialized that contains list of catalog items which is a proxy but the transaction has already ended to get that.
So create a new model ('CatalogResource' similar to catalog but without the list of items).
Then create a catalogResource object out of the Catalog (which is returned from the query)
public class CatalogResource {
private int catalog_id;
private String name;
private List<Order> orders;
}
I think the best solution to your problem (which also is the simplest) is to set your FetchType to LAZY and simply annotate the oneToMany collection fields using #transient.
Setting FetchType to EAGER isn't a good idea most times.
Best of luck.
"You don't serialize in your JSON the children entities by using the com.fasterxml.jackson.annotation.JsonIgnore property"
Add #JsonIgnore for hibernate lazy loading properties eg. #ManyToOne. That should work
I have the problem, I am using Spring Data Rest. So i have some Domain Model like this:
#Entity
public class Sample implements Serializable {
#Id
#GeneratedValue
private Long id;
#Column(name = "name")
private String name;
#Column(name = "is_main")
private Boolean isMain;
#LastModifiedDate
#Column(name = "last_modified")
private Date lastModified;
#ManyToOne
#JoinColumn(nullable = false,name = "user_id")
private User user;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(nullable = false,name="state_id")
private SampleState state;
}
So from client I am sending POST request to http://{server.host}:8080/samples
to create Sample with JSON:
{"name":"sample","user":{"id":1},"state":{"id":1}}
Hoping that it will automatically map "user" of json to Model param "user", but HttpMessageConverter just ignore the "state" JSON Object and "user" Json Object. Can you help how can I manage to Customize converter or any other ways to create sample?
Thanks in advance!
What you mean by ignore?
BTW, if you have 'user' and 'state' objects with the 'id' value '1'; then the association should persisted with new sample object successfully in the database.
But if you want to create the whole object graph into db, then the id value should be null and perhaps you might need to setup the proper cascade options.
For the relationship of many-to-one, one-to-many or even many-to-many, how to get an object without the objects included on the other side?
Say a group of address and a group of people, the relationship would be many-to-many, what if i just wanne get a "people" without the concerned address?
Classroom.java
#Entity
public class Classroom {
public Classroom(){
}
public Classroom(long id, String name) {
this.id = id;
this.name = name;
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long id;
#Column(name = "name")
private String name;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name = "classroom_people",
joinColumns = {#JoinColumn(name = "classroom_id", referencedColumnName = "id") },
inverseJoinColumns = {#JoinColumn(name = "people_id", referencedColumnName = "id") })
private List<People> peoples = new ArrayList<>();
...
// getter() and setter()
}
People.java
#Entity
public class People{
public People(){
}
public People(long id, String name) {
this.id = id;
this.name = name;
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="id")
private long id;
#Column(name = "name")
private String name;
#ManyToMany(mappedBy = "peoples", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Classroom> classrooms = new ArrayList<>();
...
// getter() and setter()
}
By using hibernate, the table of Classroom, People and the join table classroom_people have been created automatically. Until now, everything is fine.
Then by using spring repository, I tried to fetch a people by passing the name, and return a JSON object.
What I hope to get is just a JSON including people.id and people.name but what I get is a JSON including one people and all the classroom in the Set, which includes all the concerning peoples.
SO I guess the problem is the List and List and what I should do is to create three tables: classroom, people and classroom_people, then store the relationship manually to join table. Then I can fetch a simple object without problem.
AM I right?
I use Spring Repository and findAllByName(), so the SQL is generated by spring. Just wonder whether this would be the source of problem.
It's easily done with HQL if you keep the relationships bidirectional.
Something like:
String hql = "select addr.people from " + Address.class.getName() + " addr where addr.name(or some other attribute) = :addressName"
//set parameter "addressName"
EDIT:
Now you took a different example, but the above query applies the same, for example getting people from a classroom.
About your last note, getting a People by name will fetch only People instance, because the #ManyToMany List referencing classrooms is LAZY which means is not populated until you call people.getClassRooms()(if you have the session open it will load the empty list with another query)
You don't need to create the junction table yourselves, not for this purpose.
How do you know that List<ClassRoom> is populated when you fetch a People? Because from your mappings it looks OK, unless you're calling people.getClassRooms()