CrudRepository: find by multiple related entities - java

I'm having some trouble designing a query in a CrudRepository.
I have two entities, CourseOffering and Department (only relevant code shown):
CourseOffering.java:
public class CourseOffering implements Serializable
{
private Department department;
#ManyToOne(fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "DepartmentId", nullable = true)
#JsonProperty
public Department getDepartment()
{
return this.department;
}
public void setDepartment(Department department)
{
this.department = department;
}
}
Department.java:
public class Department implements Serializable
{
private Set<CourseOffering> courses;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "department")
public Set<CourseOffering> getCourses() {
return this.courses;
}
public void setCourses(Set<CourseOffering> courses) {
this.courses = courses;
}
}
and the CrudRepository in question:
CourseOfferingRepository.java:
import java.util.List;
import edu.ucdavis.dss.dw.entities.CourseOffering;
import org.springframework.data.repository.CrudRepository;
public interface CourseOfferingRepository extends CrudRepository<CourseOffering, Long>
{
CourseOffering getOneByTermIdAndNumberAndDepartmentId(long termId, String number,
long departmentId);
List<CourseOffering> findByDepartmentCode(String deptCode);
//List<CourseOffering> findAllByDepartmentCode(String deptCodes);
List<CourseOffering> findByTermCode(String termCode);
}
The three functions in CourseOfferingRepository which are not commented out work as expected. I am trying to get the fourth to work.
What I'd like to do is be able to return all CourseOfferings where the department code is one of many department codes. Note that the CourseOffering table itself only holds a department_id integer which references the ID in the Department table, where the actual deptCode is stored.
How would I go about getting that commented out CrudRepository function to work properly? Or put another way, how does one make the plural version of "List findByDepartmentCode(String deptCode);"?
Thanks in advance for any advice you can offer.

You need to change the commented out code to:
List<CourseOffering> findByDeptCodeIn(Collection<String> deptCodes)
Check out this part of the documentation to see what other keywords are allowed

As geoand pointed out in the comments, the answer is:
List<CourseOffering> findByDepartmentCodeIn(List<String> deptCodes);
Thanks geoand!

Related

Infinite recursion for null ids when using #JsonIdentityInfo

I have 2 DTO bidirectional structures Category and Product where Product is the many side in one-to-many relationship. I want to transfer them as JSON to the front-end layer by REST. I don't have any problems when ids are already assigned (for update operation), but I face well-known infinite recursion when ids are empty (create).
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property="categoryId")
public class CategoryDTO implements Serializable {
private Long categoryId;
private String categoryName;
private List<ProductDTO> products = new LinkedList<>();
public void addProduct(ProductDTO product) {
products.add(product);
product.setCategory(this);
}
// remove synchronization method, setters, getters
}
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property="productId")
public class ProductDTO implements Serializable {
private Long productId;
private String productName;
private CategoryDTO category;
// setters, getters
}
However, when I use #JsonManagedReference and #JsonBackReference all is fine. I receive beautiful json:
{
"categoryId":null,
"categoryName":"category_name",
"products": [
{
"productId":null,
"productName":"product1"
}
]
}
public class CategoryDTO implements Serializable {
private Long categoryId;
private String categoryName;
#JsonManagedReference
private List<ProductDTO> products = new LinkedList<>();
public void addProduct(ProductDTO product) {
products.add(product);
product.setCategory(this);
}
// remove synchronization method, setters, getters
}
public class ProductDTO implements Serializable {
private Long productId;
private String productName;
#JsonBackReference
private CategoryDTO category;
// setters, getters
}
In both examples the rest side is following:
#RestController
public class CategoryController {
#GetMapping(path = "/categories")
public ResponseEntity<CategoryDTO> fetchCategories() {
CategoryDTO category = new CategoryDTO();
category.setCategoryName("category_name");
ProductDTO product1 = new ProductDTO();
product1.setProductName("product1");
category.addProduct(product1);
return new ResponseEntity<>(category, HttpStatus.OK);
}
}
Why #JsonManagedReference and #JsonBackReference work, but #JsonIdentityInfo don't?
Thanks for reading.
#JsonManagedReference and #JsonBackReference are using object instance reference, so it works, when you add Product to Category, while #JsonIdentityInfois using id from value of the field.
The answer is in the class WritableObjectId and BeanSerializerBase#_serializeWithObjectId(Object, JsonGenerator, SerializerProvider, boolean).
In other words, for non-null fields serializer remembers, that he serialized given class instance, but he cannot do that for null fields. You can see that, when you hit endpoint /categories. Before the connection is interrupted, infinite JSON is generated.
Please someone correct me, if I'm wrong.
NOTE: Imo you should just remove field private CategoryDTO category or change it to private Long categoryId and you will get rid of any annotation :D Also you won't have any problem with infinite recursion.

Unable to display data from relationships in templates

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).

How to insert ManyToOne relationship record in hibernate?

I have two classes as following,
Human.java
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class Human implements Serializable {
private long id;
private String name;
....
}
Student.java
#Entity
#DynamicUpdate
public class Student extends MyFactories {
private List<Know> KnowList;
#OneToMany(fetch = FetchType.LAZY)
public List<Know> getKnowlist() {
return knowlist;
}
public void setKnowlist(List<Know> KnowList) {
return Knowlist;
}
}
Know.java
#Entity
public class Know implements Serializable {
private long id;
private Human hu;
private Student st;
....
#ManyToOne
public Person getHu() {
return hu;
}
#ManyToOne
public Client getSt() {
return st;
}
.... setters .....
}
Code
Know kw = new Know();
kw.setSt(studentObject);
kw.setHu(humanObject);
session.save(kw);
tx.commit();
I am able to insert into Know table but hibernate does not insert any record to student_know table which it has created.
I have found this answer but it says I need to use that method if I always want to retrieve all the records. Which I do not (at times, I may just need to retrieve the student class not list of its know)
System.out.println(this.student.getKnowList().size());
When I try to access the list it runs into following exception.
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.myproject.Student.knowList, could not initialize proxy - no Session
for select case change that #OneToMany(fetch = FetchType.LAZY) to #OneToMany(fetch = FetchType.EAGER) so you can get data inside it's list.
and for the insert i need your clarification about where is your relation or getter setter of the private Factory fac;?
you should have at least something like this :
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "YOUR_FACTORY_ID_COLUMN")
private Factory fac;
public Factory getFac(){
return fac;
}
public void setFac(Factory fac){
this.fac=fac;
}
and did factory have any id?
You need to use session.Update(studentObject) as well, to insert a row into student_know table.
Please also be aware that access to a lazy association outside of the context of an open Hibernate session will result in an exception. Link

Join Table and OneToMany relation in Hibernate

I created two entities for the tables HOBBY and STUDENT, but I have difficulties to retrieve the information I need from the Join Table.
This is the schema:
STUDENT STUDENT_HOBBY HOBBY
-------- ----------- --------
id ------------------ student_id
name hobby_id ---------- id
lastname hobby_name
--------- ---------- --------
where
Student to student_hobby = one to many
Hobby to student_hobby = one to many
(that means a student may have many hobbies and a hobby can belong to more than one student)
This is the Student entity class:
#Entity
#Table(name="STUDENT")
public class Student {
#OneToMany
#JoinTable(name="student_hobbies", joinColumns=#JoinColumn(name="student_id"),
inverseJoinColumns=#JoinColumn(name="hobby_id"))
private Collection<Hobby> hobbies;
#Id
#Column(name="ID")
#GeneratedValue
private Integer id;
#Column(name="NAME")
private String name;
#Column(name="LASTNAME")
private String lastName;
// Getters and setters here
}
This is the Hobby entity class:
#Entity
#Table(name="HOBBY")
public class Hobby {
#OneToMany
#JoinTable(name="student_hobbies", joinColumns=#JoinColumn(name="hobby_id"),
inverseJoinColumns=#JoinColumn(name="student_id"))
private Collection<Student> students;
#Id
#Column(name="ID")
#GeneratedValue
private Integer id;
#Column(name="HOBBY_NAME")
private String hobby_name;
// Getters and setters here
}
Now I would like to implement the following DAO, but I don't know exactly how to do:
public interface MyDAO {
public void addHobbyForStudent(int student_id, int hobby_id);
public void RemoveHobbyForStudent(int student_id, int hobby_id);
}
Should I create an other entity class for the Join Table? Could someone give me some indication on the way to follow?
You can create an entity class for the join table, but you don't need to and you probably shouldn't. This will just create more code to maintain. You should create an entity for the join table if there is data in the join table you need to query. EG: if there was a start_time or something in the join table and you wanted to be able to see the start_time.
You should be using the #ManyToMany annotation when you're avoiding a join table entity. The documentation I linked to gives some good examples of how to do this:
Example 1:
// In Customer class:
#ManyToMany
#JoinTable(name="CUST_PHONES")
public Set<PhoneNumber> getPhones() { return phones; }
// In PhoneNumber class:
#ManyToMany(mappedBy="phones")
public Set<Customer> getCustomers() { return customers; }
Example 2:
// In Customer class:
#ManyToMany(targetEntity=com.example.PhoneNumber.class)
public Set getPhones() { return phones; }
// In PhoneNumber class:
#ManyToMany(targetEntity=com.example.Customer.class, mappedBy="phones")
public Set getCustomers() { return customers; }
Example 3:
// In Customer class:
#ManyToMany
#JoinTable(name="CUST_PHONE",
joinColumns=
#JoinColumn(name="CUST_ID", referencedColumnName="ID"),
inverseJoinColumns=
#JoinColumn(name="PHONE_ID", referencedColumnName="ID")
)
public Set<PhoneNumber> getPhones() { return phones; }
// In PhoneNumberClass:
#ManyToMany(mappedBy="phones")
public Set<Customer> getCustomers() { return customers; }

QueryDSL query exception

I have a problem with a QueryDSL query. Classes:
#Entity
#Table(name="project")
#Cacheable(true)
#Cache(usage= CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Project extends DomainObject implements Comparable<Project>, IconizedComponent, Commentable {
#ManyToMany(targetEntity=Student.class)
#JoinTable(name="project_student")
#Sort(type=SortType.NATURAL) //Required by hibernate
#QueryInit({"user"})
private SortedSet<Student> projectParticipants = new TreeSet<Student>();
private Project(){}
//attributes, get+set methods etc
}
#Entity
#Cacheable(true)
#Cache(usage= CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) //Hibernate specific
public class Student extends Role {
public Student(){}
//attributes, get+set methods etc
}
#Entity
#DiscriminatorColumn(name = "rolename", discriminatorType = DiscriminatorType.STRING, length = 8)
#Table(name="role", uniqueConstraints={#UniqueConstraint(columnNames={"user_id","rolename"}, name = "role_is_unique")})
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
public abstract class Role extends LazyDeletableDomainObject implements Comparable<Role> {
#ManyToOne(optional=false)
protected User user;
public Role(){}
//attributes, get+set methods etc
}
#Entity
#Table(name="user")
#Cacheable(true)
#Cache(usage= CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) //Hibernate specific
public class User extends LazyDeletableDomainObject implements Comparable<User>, IconizedComponent {
private String firstName;
private String lastName;
public User(){}
//attributes, get+set methods etc
}
Query:
private BooleanExpression authorsNameContains(String searchTerm){
QUser user = new QUser("user");
user.firstName.containsIgnoreCase(searchTerm).or(user.lastName.contains(searchTerm));
QStudent student = new QStudent("student");
student.user.eq(user);
return QProject.project.projectParticipants.contains(student);
//java.lang.IllegalArgumentException: Undeclared path 'student'. Add this path as a source to the query to be able to reference it.
}
I have also tried annotating the projectParticipants set in Project with
#QueryInit("*.*")
But that gives the same exception. Any hints?
#Timo Westkämper
#siebZ0r
Thanks for your attention. Sorry for the delayed reply and incorrectly phrased question. Actually what I wanted to do was to write a working BooleanExpression.
In combination with the annotations already made, this was what I was after:
private BooleanExpression authorsFirstNameContains(String searchTerm){
return QProject.project.projectParticipants.any().user.firstName.containsIgnoreCase(searchTerm);
}
I got this right with the help of a colleague.

Categories

Resources