I'm stuck with trying to display data for a One-to-One relationship in Twirl templates (using Play Framework Java - 2.5.10). Basically I have a User model:
package models;
import java.sql.Date;
import javax.persistence.*;
import com.avaje.ebean.Model;
#Entity
#Table(name = "users")
public class User extends Model {
#Id
#Column(name = "id")
public Long id;
#Column(name = "first_name")
public String firstName;
#Column(name = "middle_name")
public String middleName;
#Column(name = "last_name")
public String lastName;
#Column(name = "date_of_birth")
public Date dateOfBirth;
#Column(name = "sex")
public String sex;
#OneToOne
#JoinColumn(name = "time_zone_id")
public TimeZone timeZone;
public static Finder<Long, User> find = new Finder<>(User.class);
}
and the Farmer model:
package models;
import com.avaje.ebean.Model;
import javax.persistence.*;
import java.util.List;
#Entity
#Table(name="farmers")
public class Farmer extends Model {
public enum Status {INACTIVE, ACTIVE}
#Id
#Column(name="id")
public Long id;
#OneToOne(cascade = CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="user_id")
public User user;
#Column(name="profile_pic_url")
public String profilePicUrl;
#Column(name="access_url")
public String accessUrl;
#Column(name="status")
public String status = Status.INACTIVE.name();
#OneToMany(mappedBy = "farmer", targetEntity = Farm.class, fetch = FetchType.LAZY)
public List<Farm> farms;
public static Finder<Long, Farmer> find = new Finder<>(Farmer.class);
public static List<Farmer> getAllActive() {
return Farmer.find.where().eq("status", Status.ACTIVE.name()).findList();
}
}
Notice there's a one-to-one with User model with fetch type set to eager. Now, I want to display data of farmers in my template, where a farmer's name is actually the name in the associated User model.
So I did this in my controller:
public class FarmerController extends Controller {
public Result all() {
return ok(farmers.render(Farmer.getAllActive()));
}
public Result farmer(Long id, String url) {
return ok(farmer.render());
}
}
Now this gets me the right farmer data, but when I try to display the name via the User model, I get null. More specifically, writing this results in nulls (I get nullnull, actually):
<div><h4>#(farmer.user.firstName + farmer.user.lastName)</h4></div>
What am I missing?
As discussed at the comments, this is because play-enhancer does not works for views or any Scala code at all. Since Twirl compiles scala.html code to scala code, this compiled code is not touched by the enhancer.
The solution is then to manually create the get for the relationship:
public class Farmer extends Model {
public User getUser() {
return this.user;
}
}
This is Java code and then will be handled as expected. Of course, you have to change your views to use farmer.getUser instead of farm.user.
Also, as stated at the docs, byte code enhancement involves some magic. But you can avoid it at all and just use regular POJOs (with explicitly declared gets and sets).
Related
When trying to send a request, with the same "flower_id", to Postman, returns 500 with message:
"could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement."
At the same time, it does not matter if the same ids are in the same request or in different ones with different users, if one flower has already been added earlier, it is no longer possible to add it to another user.
Entity Order:
import javax.persistence.*;
import java.time.LocalDate;
import java.util.List;
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private LocalDate orderCreateDate;
private LocalDate orderCompleteDate;
#ManyToOne(fetch = FetchType.LAZY)
private User user;
#ManyToMany
private List<Flower> flower;
private Integer price;
public Order() {
}
public Order(LocalDate orderCreateDate, LocalDate orderCompleteDate, User user, List<Flower> flower) {
this.orderCreateDate = orderCreateDate;
this.orderCompleteDate = orderCompleteDate;
this.user = user;
this.flower = flower;
}
//Getters and setters
}
Entity Flower:
import javax.persistence.*;
#Entity
#Table(name = "flowers")
public class Flower {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Integer price;
public Flower() {
}
public Flower (String name, Integer price) {
this.name = name;
this.price = price;
}
//Getters and Setters
}
OrderService:
import com.learning.flowershop.Entity.Order;
import com.learning.flowershop.Repositories.OrderRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.List;
#Service
public class OrderService {
private final OrderRepository orderRepository;
#Autowired
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
public List<Order> getAllOrdersByUserId(Long userId) {
return orderRepository.findAllByUserId(userId);
}
#Transactional
public void saveOrder(Order order) {
orderRepository.save(order);
}
}
Did you check the constraints in your database? The 500 error indicates an internal server error. It seems like there might be a unique constraint in your relation table which causes an SQL exception. If this exception is not properly caught it will get rethrown as an internal server error.
I still don't fully understand why this is the case, but I still want to leave a solution to my question.
It was only worth adding a save method for User
public User saveUser(User user) {
return userRepository.save(user);
}
Which is strange, because before that all my users were quietly saved.
I try to make query from two tables (Statement, AppCurContract), but receive a lot of repeated records. Even if I make query only from Statement, I receive the same result.
It started when I added appCurContracts field to the Statement bean.
I found same question here Spring Data JPA query return repeated row instead of actual data, why?
But I have unique key in both tables. What am I doing wrong?
Here is my code
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
#Entity
#Table
#Data
#EqualsAndHashCode(exclude = "appCurContracts")
public class Statement {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String numStatement;
#Column(updatable = false)
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateTimeSubmStatement;
private int filialId;
private int myself;
private int status;
private Date modifyDate;
private String nameNonResident;
private String email;
private Integer typeStatement;
#OneToMany(mappedBy = "statement", cascade = CascadeType.ALL)
private Set<AppCurContract> appCurContracts;
public Statement() {
super();
}
public Statement(String nameDocument, String numStatement, LocalDateTime dateTimeSubmStatement, String jurPerson, String iin_bin, int filialId, int myself, int status, Date modifyDate, String nameNonResident, String contractNum, Date contractDate, String phone, String email, Integer typeStatement, String json, String iinBinRight, AppCurContract... appCurContracts) {
this.numStatement = numStatement;
this.dateTimeSubmStatement = dateTimeSubmStatement;
this.filialId = filialId;
this.myself = myself;
this.status = status;
this.modifyDate = modifyDate;
this.nameNonResident = nameNonResident;
this.email = email;
this.typeStatement = typeStatement;
this.appCurContracts = Stream.of(appCurContracts).collect(Collectors.toSet());
this.appCurContracts.forEach(x -> x.setStatement(this));
}
public void setAppCurContracts(Set<AppCurContract> appCurContracts) {
for (AppCurContract child : appCurContracts) {
child.setStatement(this);
}
this.appCurContracts = appCurContracts;
}
}
import lombok.Data;
import javax.persistence.*;
#Entity
#Data
public class AppCurContract {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
#JoinColumn()
private Statement statement;
private String jurPerson;
private String iin_bin;
private String nameDocument;
private String contractNum;
private String contractDate;
public AppCurContract() {
super();
}
public AppCurContract(String jurPerson, String iin_bin, String nameDocument, String contractNum, String contractDate) {
this.jurPerson = jurPerson;
this.iin_bin = iin_bin;
this.nameDocument = nameDocument;
this.contractNum = contractNum;
this.contractDate = contractDate;
}
}
public interface StatementRepo extends JpaRepository<Statement, Long> {
#Query("SELECT d FROM Statement d JOIN d.appCurContracts e" +
" WHERE d.status = ?1")
// #Query("SELECT d FROM Statement d WHERE d.status = ?1")
List<Statement> findByStatus(Integer status);
List<Statement> findStatementsByEmailEquals(String email);
}
EDIT
Looking carefully at the JSON result I discovered that the result is not just repeated, but the field "appCurontract" contains nested statements then again "appCurContract" (nested in each other), etc. I think so indefinitely.
I expect only 5 records.
Define the hashcode method yourself, with whatever condition necessary as set uses this to check for duplicates. Your code (in your repository implementation) will then become:
List<Statement> findDistinctByEmail(String email);
And you should not need the Query annotation.
Remove the setAppCurContracts method on Statement class. Is not necessary.
Remove the #Query's annotation and use the findByStatus method.
I found a solution to the problem. I added #JsonIgnore annotation to the Statement field in AppCurContract class.
public class AppCurContract {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#JsonIgnore
#ManyToOne
#JoinColumn()
private Statement statement;
private String jurPerson;
private String iin_bin;
private String nameDocument;
private String contractNum;
private String contractDate;
Thank you all for participating
I have these Objects:
#Data
#Entity
#Table
#EqualsAndHashCode(callSuper = true)
public class User extends AbstractEntity implements Serializable {
private static final long serialVersionUID = -55089179131569489L;
private String username;
private String email;
private boolean admin;
private String name;
private String surname;
#OneToMany(mappedBy = "owner")
private List<Ad> ads;
}
and
#Entity
#Table
#Data
#EqualsAndHashCode(callSuper = true)
public class Ad extends AbstractEntity implements Serializable {
private static final long serialVersionUID = -4590938091334150254L;
private String name;
private String description;
private double price;
#Enumerated(EnumType.STRING)
private Category category;
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "OWNER_ID")
private User owner;
}
When I try to execute a POST with an object of type Ad.class with inside an existing object of type User.class (already in the Database) the service saves only the Ad object and the join column "OWNER_ID" remains empty.
I think that the mapping is correct. Could you help me to figure out the problem?
This is my Repository:
#Repository
#Transactional(readOnly = true)
public interface AdRepository extends PagingAndSortingRepository<Ad, String>
{}
and this is my RestRepository
#RepositoryRestResource(collectionResourceRel = "ad", path = "ad")
public interface AdRestRepository extends PagingAndSortingRepository<Ad, String> {}
If I step back a little and generalize your problem,
You are trying to POST a sub resource and expect both actions of
making a new resource (Ad)
making association with the owner (User)
to be happened with a single call.
But unfortunately spring-data-rest does not support such a behavior. You need 2 calls to do this.
One to make the resource (Ad) => POST to /ads with actual payload
Second to make the association => POST to users/{ownerId} with the hateoas link of the resource created by the first call.
Take a look at this section of official documentation.
To demonstrate my problem, I created a simple Spring Boot application. It has one Entity, which has ID, two String properties and two Sets<String> sets.
package com.mk.cat.domain;
import javax.persistence.*;
import java.util.Set;
#Entity
#Table(name = "cat")
public class Cat {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "sex")
private String sex;
#ElementCollection(fetch = FetchType.EAGER)
#Column(name = "color")
#CollectionTable(
name = "cat_color",
joinColumns = #JoinColumn(name = "cat_id"))
private Set<String> colors;
#ElementCollection(fetch = FetchType.EAGER)
#Column(name = "nickname")
#CollectionTable(
name = "cat_nickname",
joinColumns = #JoinColumn(name = "cat_id"))
private Set<String> nicknames;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Set<String> getColors() {
return colors;
}
public void setColors(Set<String> colors) {
this.colors = colors;
}
public Set<String> getNicknames() {
return nicknames;
}
public void setNicknames(Set<String> nicknames) {
this.nicknames = nicknames;
}
}
There is also a simple code, which persists and loads the Cat Entity from DB.
package com.mk.cat;
import com.google.common.collect.Sets;
import com.mk.cat.domain.Cat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class CatApplication implements CommandLineRunner {
private final CatRepository catRepository;
private static final Logger LOGGER = LoggerFactory.getLogger(CatApplication.class);
#Autowired
public CatApplication(CatRepository catRepository) {
this.catRepository = catRepository;
}
public static void main(String[] args) {
SpringApplication.run(CatApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
Cat cat = new Cat();
cat.setName("Ben");
cat.setSex("Male");
cat.setNicknames(Sets.newHashSet("Fluffy", "Mr. Tomcat", "Catburger"));
cat.setColors(Sets.newHashSet("Black", "White"));
final Cat saved = catRepository.save(cat);
LOGGER.info("Cat saved={}", cat);
catRepository.findOne(saved.getId());
}
}
I traced Hibernate and I found, that the Cat is loaded from DB by this SQL.
select cat0_.id as id1_0_0_,
cat0_.name as name2_0_0_,
cat0_.sex as sex3_0_0_,
colors1_.cat_id as cat_id1_1_1_,
colors1_.color as color2_1_1_,
nicknames2_.cat_id as cat_id1_2_2_,
nicknames2_.nickname as nickname2_2_2_
from cat cat0_
left outer join cat_color colors1_ on cat0_.id=colors1_.cat_id
left outer join cat_nickname nicknames2_ on cat0_.id=nicknames2_.cat_id
where cat0_.id=1
The Hibernate then gets this Cartesian product from the rows of the cat table and two tables, that represent the Cat#colors and Cat#nicknames sets.
id1_0_0_ name2_0_0_ sex3_0_0_ cat_id1_1_1_ color2_1_1_ cat_id1_2_2_ nickname2_2_2_
1 Ben Male 1 Black 1 Fluffy
1 Ben Male 1 Black 1 Catburger
1 Ben Male 1 Black 1 Mr. Tomcat
1 Ben Male 1 White 1 Fluffy
1 Ben Male 1 White 1 Catburger
1 Ben Male 1 White 1 Mr. Tomcat
Hibernate then goes through each and every line, parses every single item of the ResultSet and creates the Entity. Is it somehow possible to optimize this approach? I would like to select the Cat#colors and Cat#nicknames sets by a subselect, due to serious performance problems. In the real case, I fetch 1500 Entities, that have complex structure and it is not uncommon, that one fetched Entity generates 25.000 rows in the corresponding ResultSet causing a very long parsing time.
The lazy loading in my case is not the option I would like to use, because it brings clutter to the code. As far as I know, the lazily loaded Collection must be initialized by first call and this is quite a big usability price to pay in my real application.
I would appreciate 3 separate selects, one from the cat table, one from the cat_color table and one from the cat_nickname table.
I found the solution for Hibernate, the #Fetch(FetchMode.SELECT) did the work, because it made Hibernate to select the nicknames by a separate select instead of join.
#Fetch(FetchMode.SELECT)
#ElementCollection(fetch = FetchType.EAGER)
#Column(name = "nickname")
#CollectionTable(
name = "cat_nickname",
joinColumns = #JoinColumn(name = "cat_id"))
private Set<String> nicknames;
I'm trying to fetch just a part of the model using Ebean in Play! Framework, but I'm having some problems and I didn't found any solutions.
I have these models:
User:
#Entity
#Table(name = "users")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class User extends Model{
#Id
private int id;
#NotNull
#Column(name = "first_name", nullable = false)
private String firstName;
#Column(name = "last_name")
private String lastName;
#NotNull
#Column(nullable = false)
private String username;
#NotNull
#Column(nullable = false)
private String email;
private String gender;
private String locale;
private Date birthday;
private String bio;
#NotNull
#Column(nullable = false)
private boolean active;
private String avatar;
#Column(name = "created_at",nullable = false)
private Date createdAt;
#OneToMany
private List<UserToken> userTokens;
// Getters and Setters omitted for brevity
}
UserToken:
#Entity
#Table(name = "user_tokens")
public class UserToken extends Model {
#Id
private int id;
#Column(name = "user_id")
private int userId;
private String token;
#Column(name = "created_at")
#CreatedTimestamp
private Date createdAt;
#ManyToOne
private User user;
// Getters and Setters omitted for brevity
}
And then, I have a controller UserController:
public class UserController extends Controller{
public static Result list(){
User user = Ebean.find(User.class).select("firstName").where().idEq(1).findUnique();
return Results.ok(Json.toJson(user));
}
}
I expected that, when using the .select(), it would filter the fields and load a partial object, but it loads it entirely.
In the logs, there is more problems that I don't know why its happening.
It is making 3 queries. First is the one that I want. And then it makes one to fetch the whole Model, and another one to find the UserTokens. I don't know why it is doing these last two queries and I wanted just the first one to be executed.
Solution Edit
After already accepted the fact that I would have to build the Json as suggested by #biesior , I found (out of nowhere) the solution!
public static Result list() throws JsonProcessingException {
User user = Ebean.find(User.class).select("firstName").where().idEq(1).findUnique();
JsonContext jc = Ebean.createJsonContext();
return Results.ok(jc.toJsonString(user));
}
I render only the wanted fields selected in .select() after using JsonContext.
That's simple, when you using select("...") it always gets just id field (cannot be avoided - it's required for mapping) + desired fields, but if later you are trying to access the field that wasn't available in first select("...") - Ebean repeats the query and maps whole object.
In other words, you are accessing somewhere the field that wasn't available in first query, analyze your controller and/or templates, find all fields and add it to your select (even if i.e. they're commented with common HTML comment in the view!)
In the last version of Play Framework (2.6) the proper way to do this is:
public Result list() {
JsonContext json = ebeanServer.json();
List<MyClass> orders= ebeanServer.find(MyClass.class).select("id,property1,property2").findList();
return ok(json.toJson(orders));
}