Related
In codebase where model classes use Model from https://github.com/baomidou/mybatis-plus
public class SomeDomainClass extends Model<SomeDomainClass> {
How to mock (simulate) MyBatis-Plus data access layer for unit testing?
Longer code example
package com.company.project.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import lombok.experimental.Tolerate;
import java.io.Serializable;
import java.math.BigDecimal;
#Data
#EqualsAndHashCode(callSuper = false)
#Accessors(chain = true)
#TableName("t_order")
#Builder
public class Order extends Model<Order> {
private static final long serialVersionUID = 1L;
#ApiModelProperty(name = "id" , value = "id")
#TableId(value = "f_id", type = IdType.AUTO)
private Long id;
#ApiModelProperty(name = "orderId" , value = "orderId")
private Long orderId;
i'm not a professional in spring boot / hibernate jpa, so i apologize in advance.
I have 2 Entities, one called Document and the other called Bgo. Document is the parent table and Bgo the child. So my objective is to create the Document and after that, Bgo will receive it's id, for example:
When i create the first Document, it will have id_document = 1, so after that, the Bgo will have id_document = 1 as well.
I want to populate the child's id with the parent's id.
Here is the Parent Entity:
package com.testing.testing.models;
import java.io.Serializable;
import java.sql.Date;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;
#Entity
#Table(name = "DOCUMENT")
public class Document implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id_document;
private int num_document;
private Date date;
#OneToOne(mappedBy = "document", cascade = CascadeType.ALL)
private Bgo bgo;
public Document() {
}
public Document(Bgo bgo) {
this.id_document = bgo.getId_document();
this.bgo = bgo;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public Bgo getBgo() {
return bgo;
}
public void setBgo(Bgo bgo) {
this.bgo = bgo;
}
public long getId_document() {
return id_document;
}
public void setId_document(long id_document) {
this.id_document = id_document;
}
public int getNum_document() {
return num_document;
}
public void setNum_document(int num_document) {
this.num_document = num_document;
}
}
And Child Entity:
package com.testing.testing.models;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;
#Entity
#Table(name = "BGO")
public class Bgo implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private long id_document;
private String name_bgo;
private int num_bgo;
#OneToOne
#PrimaryKeyJoinColumn(name = "id_document", referencedColumnName = "id_document")
private Document document;
public Bgo() {
}
public Bgo(Document document) {
this.id_document = document.getId_document();
this.document = document;
}
public long getId_document() {
return id_document;
}
public void setId_document(long id_document) {
this.id_document = id_document;
}
public String getName_bgo() {
return name_bgo;
}
public void setName_bgo(String name_bgo) {
this.name_bgo = name_bgo;
}
public int getNum_bgo() {
return num_bgo;
}
public void setNum_bgo(int num_bgo) {
this.num_bgo = num_bgo;
}
public Document getDocument() {
return document;
}
public void setDocument(Document document) {
this.document = document;
}
}
I used the term PrimaryKeyJoinColumn cause i want Bgo's id to be the Document's id, thats why i also used the same name "id_document". So Bgo's id will be Primary and Foreign at the same time (if it is wrong please tell me a better way to do it, knowledge is always welcoming)
I have also Document's Repository:
package com.testing.testing.repository;
import com.testing.testing.models.Document;
import org.springframework.data.jpa.repository.JpaRepository;
public interface DocumentRepository extends JpaRepository<Document, Long> {
}
And Bgo's:
package com.testing.testing.repository;
import com.testing.testing.models.Bgo;
import org.springframework.data.jpa.repository.JpaRepository;
public interface BgoRepository extends JpaRepository<Bgo, Long> {
}
Document's Controller:
package com.testing.testing.controllers;
import java.util.List;
import com.testing.testing.models.Document;
import com.testing.testing.repository.DocumentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping(value="/document")
public class DocumentController {
#Autowired
DocumentRepository documentRepository;
#GetMapping
public List<Document> listDocument() {
return documentRepository.findAll();
}
#PostMapping
public Document createDocument(#RequestBody Document document) {
return documentRepository.save(document);
}
}
Bgo's Controller:
package com.testing.testing.controllers;
import java.util.List;
import com.testing.testing.models.Bgo;
import com.testing.testing.repository.BgoRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping(value="/bgo")
public class BgoController {
#Autowired
BgoRepository bgoRepository;
#GetMapping
public List<Bgo> listBgo() {
return bgoRepository.findAll();
}
}
So basically, the problem is this:
enter image description here
When i create a new Document and also the Bgo object, Bgo's id is 0, as you can see in the image, the object Bgo has the "id_document = 0 ", shouldn't it be id_document = 1 ?
And when i try to list Bgo, it still shows id_document = 0:
enter image description here
That's how i want the database to be:
enter image description here
As you can see, they are different tables. But Document has the primary key and is the parent, and Bgo is a child cause it is receiving Document's id. As you can see, Bgo's has the Document's id, it is primary and foreign at the same time, thats why i used PrimaryKeyJoinColumn. Both of them have the same id, Document's id equals Bgo's id. So whenever i create a Document and a Bgo at the same time, both of them should have the same id.
you might need to use the inheritance functionality comes with Spring instead. have a look at this:
https://www.baeldung.com/hibernate-inheritance
you can for instance use the #Inheritance(strategy = InheritanceType.SINGLE_TABLE)
on your parent table. and then you will annotate the child with just the #Entity
and then you will extend it as you normally do in a normal Inheritance scenario
so your parent class will look like so:
import java.sql.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.Table;
import javax.persistence.InheritanceType;
#Entity
#Table(name = "Document")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Document {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Id")
private Integer Id;
#Column(name = "num_document")
private int num_document;
#Column(name = "date")
private Date date;
public Document() {
}
public Document( int num_document, Date date) {
super();
this.num_document = num_document;
this.date = date;
}
}
and the child will look like so
import java.sql.Date;
import javax.persistence.Entity;
import javax.persistence.Table;
#Entity
#Table(name = "Bgo")
public class Bgo extends Document {
#Column(name = "name_bgo")
private String name_bgo;
#Column(name = "num_bgo")
private int num_bgo;
public Bgo(String name_bgo, int num_bgo) {
super();
this.name_bgo = name_bgo;
this.num_bgo = num_bgo;
}
public Bgo() {
super();
}
}
and you will have One JPARepository which looks like so;
import org.springframework.data.jpa.repository.JpaRepository;
public interface DocumentRepository extends JpaRepository<Document, Integer> {
}
the result of this code, will generate one table in your database with one extra column dtype which will identify the record to be either Bgo or any other type you might need to add in the future. this is the idea behind inheritance anyway
I hope this helped
Here are the three different inheritance strategy you have using spring
Single mapping is the default mapping
And it uses #inheritance(strategy = InheritanceType.SINGLE_TABLE) annotation to the parent class
In this strategy, the parent class is a table and all its children will be specified in a discriminator column in the parent table. The column called dtype and it contain the name of the entity as a value.
Table per the class strategy is similar to the superclass strategy but the superclass is also an entity you need to avoid this one if you want to make so many join queries
Joined table strategy is used the same as above. In this strategy, the subclasses and the superclass will all be tables in the database, but the subclasses will not inherit the filed of the superclass, it is useful if we want to apply data integrity and null constrains on some field
for more details look here
https://thorben-janssen.com/complete-guide-inheritance-strategies-jpa-hibernate/
choose which one will suit your case and then you can use the implementation I provided in the first answer and you only need to change the this annotation
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
I am working on converting a jpa entity to use lombok. The resulting code is the following:
#Entity
#Table(name = "TEST")
#Data
#NoArgsConstructor
#AllArgsConstructor
class Test {
...
#Column(name = "FORMATTING")
#Enumerated(EnumType.ORDINAL)
private FormatType formatType;
...
}
The resulting error message contains the following
Caused by: org.hibernate.HibernateException: Missing column: formatType in TEST
I am really not sure what to google here. (I tried pasting everything before formatType into google - didn't see anything)
NOTE:
fields have been renamed and aspects which do not appear relevant have been omitted, for the sake of brevity and privacy. if something looks like a typo, it probably is. please let me know if you notice something so that i can address it.
the 3 lines describing the field are unchanged from the code i'm working with
EDIT:
I just noticed this right before the error message
13:22:19,967 INFO [org.hibernate.tool.hbm2ddl.TableMetadata] (ServerService Thread Pool -- 57) HHH000261: Table found: TABLE
13:22:19,967 INFO [org.hibernate.tool.hbm2ddl.TableMetadata] (ServerService Thread Pool -- 57) HHH000037: Columns: [..., formatType, ...]
13:22:19,968 ERROR [org.jboss.msc.service.fail] (ServerService Thread Pool -- 57) MSC000001: Failed to start service jboss.persistenceunit."...": org.jboss.msc.service.StartException in service jboss.persistenceunit."...": javax.persistence.PersistenceException: [PersistenceUnit: ...] Unable to build EntityManagerFactory
Should be functional
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#Table(name = "PARENT")
public abstract class Parent implements Serializable {
private static final long serialVersionUID = 1;
#Id
#Column(name = "ID")
#GeneratedValue
private long id;
#Column(name = "ENABLED")
private boolean enabled;
}
#Entity
#Table(name = "CHILD")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Child extends Parent {
/** XXX: HERE BE DRAGONS */
#Column(name = "ENUM_1")
#Enumerated(EnumType.STRING)
private Enum1 enum1;
#Column(name = "ENUM_2")
#Enumerated(EnumType.ORDINAL)
private Enum2 enum2;
/** XXX: NO MORE DRAGONS */
#Column(name = "FREQUENCY")
private String frequency;
#Column(name = "EMPTY")
private boolean empty;
#Column(name = "MAX_SIZE")
private int maxSize;
}
public enum Enum1 {
A,
B,
C
}
public enum Enum2 {
X,
Y,
Z
}
I have rolled back the lombok changes, I would still like to know what the issue is, but there is no rush. Also, thanks to this lovely little bug i am about 4 hours behind so i may be a little slow on the responses.
The pk of the child table is an fk to the parent table, and without lombok everything appears to work, despite the fact that the Child class has no id.
SOLUTION:
I completely forgot about asking this. Not long ago I revizited this problem. To explain the solution lets look at a slightly simplified version of the first example i included.
#Entity
#Table(name = "TEST")
#Setter
#Getter
class Test {
...
#Column(name = "FORMATTING")
#Enumerated(EnumType.ORDINAL)
private FormatType formatType;
...
}
It would appear that Lombok will give you this:
#Entity
#Table(name = "TEST")
class Test {
...
#Column(name = "FORMATTING")
#Enumerated(EnumType.ORDINAL)
private FormatType formatType;
public FormatType getFormatType() {
return formatType;
}
public void setFormatType(FormatType formatType) {
this.formatType = formatType;
}
...
}
Note that the annotations are still attached to the field. Now, I am not certain if it is just the version or implementation of JPA that we are using but I gather that if an accessor is defined jpa just ignores any annotations besides #Column (as well as any parameters specified for #Column - which is why jpa was looking for the wrong column name). So we actually need:
#Entity
#Table(name = "TEST")
class Test {
...
private FormatType formatType;
#Column(name = "FORMATTING")
#Enumerated(EnumType.ORDINAL)
public FormatType getFormatType() {
return formatType;
}
public void setFormatType(FormatType formatType) {
this.formatType = formatType;
}
...
}
After a great deal of confusion trying to find examples and fill in some specifics regarding how lombok does its thing (to be fair I am very easily confused) i discovered this little gem: onMethod=#__({#AnnotationsHere}). Utilizing this feature I came up with the following:
#Entity
#Table(name = "TEST")
#Setter
class Test {
...
#Getter(onMethod=#__({
#Column(name = "FORMATTING"),
#Enumerated(EnumType.ORDINAL)
}))
private FormatType formatType;
...
}
And presto it works. Now that we have what is apparently the only available solution I would like to address the question we are all pondering at the moment: is that really any cleaner than just writing the method manually and attaching the annotations there? Answer: ... I have no idea. I am just happy I found a solution.
Its strange. Can you show more code?
I'm trying to write a simple project with part of code like in your question and it worked. I used Spring Boot and MySQL. Try to check your configuration. There is my code:
Enum:
public enum FormatType {
FIRST_TYPE, SECOND_TYPE
}
Table in MySQL:
create table TEST
(
ID int auto_increment primary key,
FORMATTING int not null
);
Entity:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
#Entity
#Table(name = "TEST")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Test {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name = "FORMATTING")
#Enumerated(EnumType.ORDINAL)
private FormatType formatType;
}
Repository:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface TestRepository extends JpaRepository<Test, Integer> {
}
Service:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
#Service
public class TestService {
private TestRepository repository;
#Autowired
public TestService(TestRepository repository) {
this.repository = repository;
}
public List<Test> getAllTestEntities() {
return repository.findAll();
}
}
Is unlikely that lombok causes runtime problems, as it works on precompile time, you might find useful to decompile the generated code, I sometimes find that the order in which lombok annotations are placed in the source code affect the final result, so, you use #Data and #NoArgsConstructor , I guess you can remove #NoArgsConstructor an try to see if that solves your problem.
I faced the same problem with Lombok and JPA but I setup the Lombok and it worked as expected. Below is the code:
Controller
package com.sms.controller;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.sms.model.StudentModel;
import com.sms.persistance.StudentRepository;
#RestController
public class StudentController {
#Autowired
private StudentRepository sr;
#PostMapping("/addstudent")
public String addStudent(#Valid #RequestBody StudentModel studentModel) {
StudentModel result = sr.save(studentModel);
return result.equals(null)?"Failed":"Successfully Saved student data";
}
}
Model
package com.sms.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.Data;
import lombok.RequiredArgsConstructor;
#Data
#RequiredArgsConstructor
#Entity
#Table(name="student", schema="development")
public class StudentModel {
#Id
#Column(name="student_id")
private int id;
#Column(name="student_name")
private String studentname;
#Column(name="student_address")
private String studentaddress;
}
Repository
package com.sms.persistance;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.sms.model.StudentModel;
#Repository
public interface StudentRepository extends JpaRepository<StudentModel, Integer>{
}
I want to write some query methods in repository layer. This method must ignore null parameters. For example:
List<Foo> findByBarAndGoo(Bar barParam, #optional Goo gooParam);
This method must be return Foo by this condition:
bar == barParam && goo == gooParam;
if gooParam not null. if gooParam was null then condition change to:
bar == barParam;
Is there any solution? Can someone help me?
I don't believe you'll be able to do that with the method name approach to query definition. From the documentation (reference):
Although getting a query derived from the method name is quite
convenient, one might face the situation in which either the method
name parser does not support the keyword one wants to use or the method
name would get unnecessarily ugly. So you can either use JPA named
queries through a naming convention (see Using JPA NamedQueries for
more information) or rather annotate your query method with #Query
I think you have that situation here, so the answer below uses the #Query annotation approach, which is almost as convenient as the method name approach (reference).
#Query("select foo from Foo foo where foo.bar = :bar and "
+ "(:goo is null or foo.goo = :goo)")
public List<Foo> findByBarAndOptionalGoo(
#Param("bar") Bar bar,
#Param("goo") Goo goo);
Too late to answer. Not sure about relationship between Bar and Goo. Check if Example can helps you.
It worked for me. I have a similar situation, entity User have set of attributes and there is findAll method which search user based on attributes(which are optional).
Example,
Class User{
String firstName;
String lastName;
String id;
}
Class UserService{
// All are optional
List<User> findBy(String firstName, String lastName, String id){
User u = new User();
u.setFirstName(firstName);
u.setLastName(lastName);
u.setId(id);
userRepository.findAll(Example.of(user));
// userRepository is a JpaRepository class
}
}
Complementing the answer of #chaserb, I personally would add the parameter as a Java8 Optional type to make it explicit in the signature of the method the semantics that is an optional filter.
#Query("select foo from Foo foo where foo.bar = :bar and "
+ "(:goo is null or foo.goo = :goo)")
public List<Foo> findByBarAndOptionalGoo(
#Param("bar") Bar bar,
#Param("goo") Optional<Goo> goo);
You can use JpaSpecificationExecutor //import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
Step 1: Implement JpaSpecificationExecutor in your JPA Repository
Ex:
public interface TicketRepo extends JpaRepository<Ticket, Long>, JpaSpecificationExecutor<Ticket> {
Step 2 Now to fetch tickets based on optional parameters you can build Specification query using CriteriaBuilder
Ex:
public Specification<Ticket> getTicketQuery(Integer domainId, Calendar startDate, Calendar endDate, Integer gameId, Integer drawId) {
return (root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
predicates.add(criteriaBuilder.equal(root.get("domainId"), domainId));
predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("createdAt"), startDate));
predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get("createdAt"), endDate));
if (gameId != null) {
predicates.add(criteriaBuilder.equal(root.get("gameId"), gameId));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
}
Step 3: Pass the Specification instance to jpaRepo.findAll(specification), it will return you the list of your entity object (Tickets here in the running example)
ticketRepo.findAll(specification); // Pass output of function in step 2 to findAll
So many great answers already, but I specifically implemented this using the answer from #Pankaj Garg (Using the Spring Specification API). There are a few use cases I am adding to my answer
4 parameters that may or may not be null.
Paginated response from the repository.
Filtering by a field in a nested object.
Ordering by a specific field.
First I create a couple of entities, specifically Ticket, Movie and Customer. Nothing fancy here:
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.UUID;
#Entity
#Table(name = "ticket", schema = "public")
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
public class Ticket implements Serializable {
#Id
#Basic(optional = false)
#NotNull
#Column(name = "id", nullable = false)
private UUID id;
#JoinColumn(name = "movie_id", referencedColumnName = "id", nullable = false)
#ManyToOne(fetch = FetchType.EAGER)
private Movie movie;
#JoinColumn(name = "customer_id", referencedColumnName = "id", nullable = false)
#ManyToOne(fetch = FetchType.EAGER)
private Customer customer;
#Column(name = "booking_date")
#Temporal(TemporalType.TIMESTAMP)
private Date bookingDate;
}
Movie:
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
#Entity
#Table(name = "movie", schema = "public")
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
public class Movie implements Serializable {
#Id
#Basic(optional = false)
#NotNull
#Column(name = "id", nullable = false)
private UUID id;
#Basic(optional = false)
#NotNull
#Size(max = 100)
#Column(name = "movie_name", nullable = false, length = 100)
private String movieName;
}
Customer:
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
#Entity
#Table(name = "customer", schema = "public")
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
public class Customer implements Serializable {
#Id
#Basic(optional = false)
#NotNull
#Column(name = "id", nullable = false)
private UUID id;
#Basic(optional = false)
#NotNull
#Size(max = 100)
#Column(name = "full_name", nullable = false, length = 100)
private String fullName;
}
Then I create a class with fields for the parameters I wish to filter by:
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Date;
import java.util.UUID;
#Data
#AllArgsConstructor
public class TicketFilterParam {
private UUID movieId;
private UUID customerId;
private Date start;
private Date end;
}
Next I create a class to generate a Specification based on the filter parameters. Note the way nested objects are accessed, as well as the way ordering is added to the query.
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class TicketSpecifications {
public static Specification<Ticket> getFilteredTickets(TicketFilterParam params) {
return (root, criteriaQuery, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
if (params.getMovieId() != null) {
predicates.add(criteriaBuilder.equal(root.get("movie").<UUID> get("id"), params.getMarketerId()));
}
if (params.getCustomerId() != null) {
predicates.add(criteriaBuilder.equal(root.get("customer").<UUID> get("id"), params.getDepotId()));
}
if (params.getStart() != null && params.getEnd() != null) {
predicates.add(criteriaBuilder.between(root.get("bookingDate"), params.getStart(), params.getEnd()));
}
criteriaQuery.orderBy(criteriaBuilder.desc(root.get("bookingDate")));
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
}
}
Next I define the Repository interface. This would have not only JpaRepository, but also JpaSpecificationExecutor:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
#Repository
public interface TicketRepository extends JpaRepository<Ticket, UUID>, JpaSpecificationExecutor<Ticket> {
}
Finally, in some service class, I obtain results like this:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
#Service
public class TicketService {
#Autowired
private TicketRepository ticketRepository;
public Page<Ticket> getTickets(TicketFilterParam params, PageRequest pageRequest) {
Specification<Ticket> specification = TicketSpecifications.getFilteredTickets(params);
return ticketRepository.findAll(specification, pageRequest);
}
}
PageRequest and TicketFilterParam would probably be obtained from some parameters and values on a rest endpoint.
You could code this yourself in just a few lines:
List<Foo> findByBarAndOptionalGoo(Bar bar, Goo goo) {
return (goo == null) ? this.findByBar(bar) : this.findByBarAndGoo(bar, goo);
}
Otherwise, I don't know if Spring-Data supports this out of the box.
It is too late too answer, but for anyone who looks for a solution yet there is a more simple way as below, I have faced the same issue and finally could find this solution that looks like very simple and efficient than the others to me:
my Controller Class:
#RestController
#RequestMapping("/order")
public class OrderController {
private final IOrderService service;
public OrderController(IOrderService service) {
this.service = service;
}
#RequestMapping(value = "/{username}/", method = RequestMethod.GET)
public ResponseEntity<ListResponse<UserOrdersResponse>> getUserOrders(
#RequestHeader Map<String, String> requestHeaders,
#RequestParam(required=false) Long id,
#RequestParam(required=false) Long flags,
#RequestParam(required=true) Long offset,
#RequestParam(required=true) Long length) {
// Return successful response
return new ResponseEntity<>(service.getUserOrders(requestDTO), HttpStatus.OK);
}
}
As you can see, I have Username as #PathVariable and length and offset which are my required parameters, but I accept id and flags for filtering search result, so they are my optional parameters and are not necessary for calling the REST service.
my Repository interface:
#Query("select new com.ada.bourse.wealth.services.models.response.UserOrdersResponse(FIELDS ARE DELETED TO BECOME MORE READABLE)" +
" from User u join Orders o on u.id = o.user.id where u.userName = :username" +
" and (:orderId is null or o.id = :orderId) and (:flag is null or o.flags = :flag)")
Page<UserOrdersResponse> findUsersOrders(String username, Long orderId, Long flag, Pageable page);
And that's it, you can see that I checked my optional arguments with (:orderId is null or o.id = :orderId) and (:flag is null or o.flags = :flag) and I think it needs to be emphasized that I checked my argument with is null condition not my columns data, so if client send Id and flags parameters for me I will filter the Result with them otherwise I just query with username which was my #PathVariable.
I am having three class. Person,vehicle and a association class to link the person and vehicle
Person
package entity;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
#Entity
#Table(name ="PERSON")
public class Person {
#EmbeddedId
private PKperson pkPerson;
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany(cascade=CascadeType.ALL,mappedBy="person")
private Set<PersonVehAssnVO> personVehAssnVOSet=new HashSet<PersonVehAssnVO>();
//getters & setters
}
PKperson
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Embeddable;
#Embeddable
public class PKperson implements Serializable {
#Column(name="NAME", nullable=false)
private String name;
#Column(name="DOB_DT", nullable=false)
private Date dobDt;
}
Vehicle
package entity;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.OneToMany;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
#Entity
#Table(name ="VEHICLE")
public class Vehicle {
#Id
#Column(name="VEHICLE_ID",unique=true, nullable=false)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_name")
#SequenceGenerator(allocationSize = 1, name = "seq_name", sequenceName = "SEQ_VEHICLE_ID")
private Long vehicleId;
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany(cascade=CascadeType.ALL,mappedBy="vehicle")
private Set<PersonVehAssnVO> personVehAssnVOSet=new HashSet<PersonVehAssnVO>();
private String vehicleName;
//getters & setters
}
Person Vehicle Association
package entity;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
#Entity
#Table(name ="PERSON_VEHICLE_ASSOC")
public class PersonVehAssnVO {
#EmbeddedId
private PKperson pkPerson;
#ManyToOne
#JoinColumns({ #JoinColumn(name = "pkPerson.name",referencedColumnName ="NAME"),
#JoinColumn(name = "pkPerson.dobDt",referencedColumnName ="DOB_DT")})
private Person person;
#ManyToOne
#JoinColumn(name = "VEHICLE_ID", referencedColumnName = "VEHICLE_ID")
private Vehicle vehicle;
//getters & setters
}
//**Save Method**
PKperson pkPerson = new PKperson();
SimpleDateFormat dtFormat = new SimpleDateFormat("yyyy-MM-dd");
Date date1 = dtFormat.parse("1984-12-14");
pkPerson.setName("Magesh");
pkPerson.setDobDt(date1);
Person person = new Person();
person.setPKperson(pkPerson);
Vehicle vehicle = new Vehicle();
vehicle.setName("Honda350");
PersonVehAssnVO perVehAssnVO = new PersonVehAssnVO();
PersonVehAssnVO.setPKperson(pkPerson);
PersonVehAssnVO.setVehicle(vehicle);
Set<PersonVehAssnVO> assocSet = new HashSet<PersonVehAssnVO>();
assocSet.add(PersonVehAssnVO);
person.setpersonVehAssnVOSet(assocSet);
vehicle.setpersonVehAssnVOSet(assocSet);
Session session = getHibernateTemplate().getSessionFactory().openSession();
transaction = session.beginTransaction();
transaction.begin();
session.save(person);
transaction.commit();
//=============================================================
While Executing the above save logic I am getting error ": [entity.PersonVehAssnVO#component[name,dobDt]{dobDt=magesh, dobDt=1984-12-14 00:00:00}]"
Show Sql Gives "select personVehAssnVO_.NAME, personVehAssnVO_.DOB_DT, personVehAssnVO_.VEHICLE_ID as vehicle_34 from PERSON_VEHICLE_ASSOC where personVehAssnVO_.NAME=? and personVehAssnVO_.DOB_DT=?"
I want to save Person,Vehicle and association in a single save means CASCADE ALL When I save Person.
Any help appreciated
Mark the date in PKPerson with the #Temporal annotation:
#Temporal(TemporalType.TIME)
#Column(name="DOB_DT", nullable=false, length = 8)
private Date dobDt;
According to the Java Doc
This annotation must be specified for persistent fields or properties
of type java.util.Date and java.util.Calendar. It may only be
specified for fields or properties of these types.
The Temporal annotation may be used in conjunction with the Basic
annotation, the Id annotation, or the ElementCollection annotation
(when the element collection value is of such a temporal type.
And according to this SO question:
In plain Java APIs, the temporal precision of time is not defined.
When dealing with temporal data you might want to describe the
expected precision in database. Temporal data can have DATE, TIME, or
TIMESTAMP precision (ie the actual date, only the time, or both). Use
the #Temporal annotation to fine tune that.
I hope this solves your problem. You can interchange the TemporalType with others that match your field type in the database. The column length also can be changed accordingly.
Edit
I think you also need to remove the mappedBy="person" and mappedBy="vehicle" from the annotation of the PersonVehAssnVO sets in the Person and Vehicle classes and replace them with full column properties so that these classes can be the owners of the relationship. Otherwise when you save a person, it will ignore updating any properties marked with the mappedby annotation because this means the Person is not the owner of that relationship.