Ebean Query by OneToMany Relationship - java

I'm using Ebean with the Play 2 Framework and got two models: a user model and a book model. The user model is connected with the book model in a OneToMany Relationship. So every user can have many books or no book at all. The book model itself has properties too. Now I want to create a query in the user model, which returns only users, who have books with certain properties. For example: One property might be condition, like new or used. Now give me all users which have books in new condition.
Is it possible to create such a query with the Ebean methods? Or do I have to use raw SQL?

Say you have the following models:
#Entity
public class User extends Model {
#Id
#Column(name = "user_index")
private int id;
#Column(name = "user_first_name")
private String firstName;
[...]
#OneToMany(mappedBy = "book_owner_index")
private List<Book> books;
public static Finder<Integer, User> find = new Finder<Integer, User>(Integer.class, User.class);
[...]
}
and
#Entity
public class Book extends Model {
#Id
#Column(name = "book_index")
private int id;
#Column(name = "book_name")
private String name;
#Column(name = "book_condition")
private String condition;
[...]
#ManyToOne
#JoinColumn(name = "book_owner_index", referencedColumnName = "user_index")
private User owner;
[...]
}
Then you can do a search like:
List<User> users = User.find.select("*")
.fetch("books")
.where()
.eq("books.condition", "new")
.findList();

List<User> users = User.find.select("*")
.fetch("books")
.where()
.eq("t1.condition", "new")
.findList();
For me, it works only when I use "t1.", I am using Postgres DB. The generated query makes sense with t1.

Related

Mapping DTO with circular self dependency

I have a entity user with self dependency. When i Map this entity to DTO I have the problem of circular dependency. .
User.class:
public class User {
#Id
#GeneratedValue
private Long id;
#NotNull
#Column(name = "first_name")
private String firstName;
#NotNull
#Column(name = "last_name")
private String lastName;
#JsonBackReference
#ManyToMany(
private List<User> friedns_of = new ArrayList<>();
#ManyToMany(cascade = CascadeType.ALL,
mappedBy = "followers")
private List<User> friends = new ArrayList<>();
UserMapper method in UserMapper:
public static UserResponse toUser(User user) {
UserResponse userResponse = new UserResponse();
userResponse.setId(user.getId());
userResponse.setFollowers(user.getFollowers().stream().map(UserMapper::toUser).toList());
userResponse.setFollowing(user.getFollowing().stream().map(UserMapper::toUser).toList());
return userResponse;
}
When i run the method toUser() I get stackOverFlowError exception caused by the infinite circular dependency. Any advise how to solve this?
One way to resolve this is to model the 'follows' relationship as a separate entity:
#Table(name="user_followers")
public class Follows {
#Id
#GeneratedValue
private Long id;
#NotNull
#Column(name = "follower_Id")
private User follower;
#NotNull
#Column(name = "user_id")
private User user;
}
Then you could give your user two one-to-many lists of these entities:
public class User {
#Id
#GeneratedValue
private Long id;
#OneToMany(mappedBy = "user_id")
private List<Follows> followers;
#OneToMany(mappedBy = "follower_Id")
private List<Follows> following;
}
EDIT: instead of the id field in Follows you could use the user_id and follower_id as a composite primary key using #Embeddable. Omitted here for brevity. See here for more details: https://www.baeldung.com/jpa-many-to-many
Since you already have a DTO of UserResponse, you are on the right path towards a correct solution. My suggestion would be to avoid #ManyToMany on an entity level, and manage followers on a service level.
This means you will have to split relation ManyToMany join column into a separate entity, such as UserFollowsEntity with fields userId and followsUserId. Then remove followers and following lists from your User entity entirely.
Now when creating UserResponse in a service, you will have to
Select the actual user from repository – userRepository.findById(userId)
Select followers – userFollowsRepository.findByFollowsUserId(userId)
Select following – userFollowsRepository.findByUserId(userId)
It is a good practice to try and avoid bidirectional in entities relationships entirely if possible.
EDIT: This will give you two lists: followers and following. You will probably want to know their user names, so what you can do is to merge followers and following lists into one, then extract all user ids from that list. Then query user repository with a list of those IDs, and just attach the required user information to your response model.
Yes it does sound like a bit more work compared to the seeming simplicity of utilizing JPA annotations, but this is the best way to avoid circular dependency as well as decouple the Follower functionality from your user entity.

How to query and persist static objects in MySQL Database with Spring Boot?

I am working on a Restful service built with Java Spring and I have some issues modeling the data. I want to store shelfs with books. The books belong to a given category. I have a POST request to store shelfs to a mysql database (via service and CrudRepository). However I am not able to store more than one book of the same category. Here are my (simplified) entities.
A Shelf with an id and a collection of books.
#Entity
public class Shelf{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "shelf")
private List<Book> books= new ArrayList<>();
...
}
The class Book is defined as follows:
#Entity
public class Book{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "category_id")
private Category category;
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JsonIgnore
private Shelf shelf;
Each book belongs to a category(e.g. thriller, fiction, etc.). Here is the category entity:
#Entity
public class Category {
private Long id;
private String name;
And finally my Controller:
#RestController
public class ShelfController {
#Autowired
private ShelfService shelfService;
#PostMapping("/shelfs")
public Shelf addShelf(#RequestBody Shelf shelf) {
return shelfService.addShelf(shelf);
}
Now here is my problem: The categories will be given and there will be no option to change these, I would therefore like to have them stored in the database or hard code them as static objects. In the Post request for new shelfs I would like to provide only the category id and make the controller find the corresponding object itself.
What I did so far was to treat the categories as a usual Entity, so whenever I added a new shelf with books having a category_id, the category was created with the given id and an empty name. But as soon as I used the same category id again, the application threw a com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '1' for key 'PRIMARY' exception. I don't want the controller to create new category objects, but instead want it to fetch the corresponding objects from a service or a static Collection.
So my question is: How can I achieve this?
Hints for solutions/tricks to improve the design are most welcome, I am new to the topic.
One solution is to create Data Transfer Objects (DTO). Example:
class ShelfDTO {
private Long id;
private List<Long> bookIds;
}
Then use this class to receive the POST requests:
#RestController
public class ShelfController {
#Autowired private ShelfService shelfService;
#PostMapping("/shelfs")
public Shelf addShelf(#RequestBody ShelfDTO shelfDto) {
return shelfService.addShelf(shelfDto);
}
}
Then modify your ShelfService to convert the DTO to an Entity:
#Service
public class ShelfService {
#Autowired private ShelfRepository shelfRepository;
#Autowired private BookRepository bookRepository;
#Transactional
public Shelf addShelf(ShelfDTO shelfDto) {
List<Book> books = bookRepository.findAllById(shelfDto.getBookIds());
return shelfService.addShelf(new Shelf(books));
}
}
Final comment: I noticed that you have a bidirectional relationship. You are responsible for keeping it in a consistent state.
The easiest way is to create the methods addTo(shelf, book) and removeFrom(shelf, book) that encapsulate the logic of both adding the book to the list in the shelf and setting the shelf in the book.

How to use AND in hibernate 5.2 Criteria?

I have two objects User and House. One user can have several houses.
Annotations from House.class:
#Entity
#Table(name="house")
public class House {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="house_id")
private int id;
#ManyToOne
#JoinColumn(name="user_id")
private User user;
#Column(name="country")
private String country;
#Column(name="city")
private String city;
#Column(name="street")
private String street;
#Column(name="post_code")
private String postCode;
#Column(name="house_number")
private String houseNumber;
#Column(name="flats")
private int flats;
#Column(name="picture")
private String picture;
#Column(name="surname_first") // order names will be written in receipt
private Boolean writeSurnameFirst;
#Column(name="cut_name") // Nado ili net sokrashat imena
private Boolean cutName;
#ManyToOne
#JoinColumn(name="currency_id")
private Currency currency;
#ManyToOne
#JoinColumn(name="documentation_lang_id")
private Languages documentationLang;
#Column(name="default_house")
private Boolean defaultHouse;
I need to get a House object based on User and column defaultHouse = true.
I tried this code, but I can't get how to implement User into it:
tx = sess.beginTransaction();
// create criteria builder
CriteriaBuilder builder = sess.getCriteriaBuilder();
// create criteria
CriteriaQuery<House> query = builder.createQuery(House.class);
// specify criteria root
Root<House> root = query.from(House.class);
query.select(root).where(builder.equal(root.get("default_house"), true)
.and(builder.equal(root.get(House.getUser), user)));
house = sess.createQuery(query).getSingleResult();
tx.commit();
In fact builder.and() takes two parameters which are the two restrictions to be joined with AND keyword in SQL, and the builder.and() should be used inside builder.where() method.
change the following code:
query.select(root).where(builder.equal(root.get("default_house"), true)
.and(builder.equal(root.get(House.getUser), user)));
Like this:
query.select(root).where(builder.and(builder.equal(root.get("default_house"), true), builder.equal(root.get(House.getUser), user))));
Please refer to Hibernate ORM 5.2.11.Final User Guide for further details and more examples.
Note:
Hibernate is concerned about objects and not tables and when we use its modules like Criteria we use attributes names in the Object and not DB columns names.
In CriteriaBuilder methods you must use the entity field names like following:
builder.equal(root.get("defaultHouse"), true)
and not the database column names:
builder.equal(root.get("default_house"), true)
I attached my example:
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<Eje> query = builder.createQuery(Eje.class);
Root<Eje> productRoot = query.from(Eje.class);
query.select(productRoot)
.where(builder.and(builder.equal(productRoot.get("id"), 1), builder.equal(productRoot.get(Eje_.factorCriticos).get(FactorCritico_.id), 1)));
result =(List<T>) session.createQuery(query).getResultList();

Hibernate Criteria One to Many issues

I am trying to use Hibernate Criteria api to fetch only the topics based on the USER_ID but have no idea how to do it using the criteria.
My Tables are "topic_users" (below)
and "topics" table (below)
I know how to do it using SQL, this would be something like:
SELECT TOPICNAME
FROM topic_users INNER JOIN topics on topic_users.TOPICS_TOPICS_ID = topics.TOPICS_ID
WHERE topic_users.USER_ID = 1
This will return all TOPICNAME of USER_ID 1 which is exactly what I want but how I can do this with Hibernate Criteria. So far I have this in my Repository class (see below) but this will only return a highly nested JSON array. I could loop through the objects, use a DTO and build my response or try the Hibernate createSQLQuery method that will let me call a native SQL statement directly (haven't tried that yet)...but I am trying to learn the Criteria so I hope anyone can answer my query.
#Repository("userTopicsDao")
public class UserTopicsDaoImpl extends AbstractDao<Integer, UserTopics>implements UserTopicsDao {
#Override
public List<UserTopics> findMyTopics(int userId) {
Criteria crit = createEntityCriteria();
crit.add(Restrictions.eq("userId", userId));
List<UserTopics> userTopicsList = (List<UserTopics>)crit.list();
return userTopicsList;
}
and my TOPIC_USERS Entity where I have mapped the TOPICS
#Entity
#Table(name="TOPIC_USERS")
public class UserTopics {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
#Column(name="TOPICUSER_ID")
private Integer id;
#Column(name="USER_ID")
private Integer userId;
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "TOPICS_ID")
private Set<Topics> topicsUser;
//getter and setters
Ok starting from the ground up.. you entity classes should look like this:
#Entity
#Table(name="TOPIC_USERS")
public class UserTopics {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
#Column(name="TOPICUSER_ID")
private Integer id;
#Column(name="USER_ID")
private Integer userId;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "TOPICS_TOPICS_ID")
private Topics topics;
Your Topics class should look like this:
#Entity
#Table(name="TOPICS")
public class Topic {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
#Column(name="TOPICUS_ID")
private Integer id;
#Column(name="TOPICNAME")
private Integer topicName;
#OneToMany(mappedBy = "topics")
private Set<UserTopics> userTopics;
Finally the Criteria:
Version 1) You get entire entity:
Criteria c = session.createCriteria(Topics.class, "topics");
c.createAlias("topics.userTopics", "userTopics");
c.add(Restrictions.eq("userTopics.userId", userId));
return c.list(); // here you return List<Topics>
Version 2) You project only the topicname:
Criteria c = session.createCriteria(Topics.class, "topics");
c.createAlias("topics.userTopics", "userTopics");
c.add(Restrictions.eq("userTopics.userId", userId));
c.setProjection(Projections.property("topics.topicName"));
List<Object[]> results = (List<Object[]>)c.list();
// Here you have to manually get the topicname from Object[] table.
}

Hibernate persist entity without fetching association object. just by id

I have an simple association between 2 entities:
public class Car {
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
...
}
and
public class User {
#Id
#GeneratedValue
#Column(name = "user_id")
private long userId;
...
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private Set<Car> cars;
...
}
Then I get some user id from client. For example, userId == 5;
To save car with user I need to do next:
User user = ... .findOne(userId);
Car car = new Car();
car.setUser(user);
... .save(car);
My question is: Can I persist car record without fetching user?
Similarly like I would do by using native SQL query: just insert userId like string(long) in Car table.
With 2nd lvl cache it will be faster but in my opinion I don't need to do extra movements. The main reason that I don't want to use native Query is because I have much more difficult associations in my project and I need to .save(car) multiple times. Also i don't want to manually control order of query executions.
If I use session.createSQLQuery("insert into .....values()") will the Hibernate's batch insert work fine?
Correct me if I'm wrong.
Thanks in advance!
UPDATE:
Actually the mapping is similar to:
There is #ManyToMany association between User and Car. But cross table is also an entity which is named, for example, Passanger. So the mapping is next:
public class User{
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", targetEntity = Passenger.class)
private Set<Passenger> passengers;
}
Cross entity
#IdClass(value = PassengerPK.class)
public class Passenger {
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "car_id")
private Car car;
... other fields ...
}
Car entity:
public class Car {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "car", targetEntity = Passenger.class, cascade = CascadeType.ALL)
private Set<Passenger> passengers;
}
And the code:
List<User> users = ... .findInUserIds(userIds); // find user records where userId is IN userIds - collection with user Ids
Car car = new Car(); //initialization of car's fields is omitted
if (users != null) {
car.setPassengers(new HashSet<>(users.size()));
users.forEach((user) -> car.getPassengers().add(new Passenger(user, car)));
}
... .save(car);
"Can I persist car record without fetching user?"
Yes, that's one of the good sides of Hibernate proxies:
User user = entityManager.getReference(User.class, userId); // session.load() for native Session API
Car car = new Car();
car.setUser(user);
The key point here is to use EntityManager.getReference:
Get an instance, whose state may be lazily fetched.
Hibernate will just create the proxy based on the provided id, without fetching the entity from the database.
"If I use session.createSQLQuery("insert into .....values()") will the Hibernate's batch insert work fine?"
No, it will not. Queries are executed immediately.
If someone is using Spring Data JPA: The same can be achieved in Spring Data JPA can be done using the method
JpaRepository.getReferenceById(ID id)
This replaced the former
getOne(ID)
Hibernate users can implement this method:
public <T extends Object> T getReferenceObject(Class<T> clazz, Serializable id) {
return getCurrentSession().get(clazz, id);
}
And call like:
MyEntity myEntity = getRefererenceObject(MyEntity.class, 1);
You can change id type to Integer or Long as per your entity model.
Or T can be inherited from your BaseEntity if you have one base class for all entities.
The following approach works for me:
User user = new User();
user.setId(userId);
car.setUser(user);

Categories