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.
Related
I'm coming back to you because I'm blocked.
I'm currently facing a MultipleBagFetchException, please find bellow my Entity Expense.
To resolve this mainly the solution can be to fetch Lazy and the mistake is disappearing but at this point, my endpoint findAll() is not working, because fetches should be EAGER for this endpoint being able to work.
In other words, on both side, I'm blocked.
Impossible for me to change List for Set as it's totally against "my business need".
If anyone is having any idea how can I fix this... :(
Thank you very much guys.
Rgds,
Erick
package net.erickcaron.mybudgetapi.models;
import lombok.*;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import javax.persistence.*;
import java.math.BigDecimal;
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#ToString
#Entity
#Table(name="expenses")
public class Expense {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name="total_amount")
private BigDecimal totalAmount;
#Column(name="shared_amount")
private BigDecimal sharedAmount;
#Column(name="personal_amount")
private BigDecimal personalAmount;
#ManyToOne(fetch = FetchType.EAGER)
private Shop shop;
#ManyToOne(fetch = FetchType.EAGER)
private Currency currency;
}
package net.erickcaron.mybudgetapi.rest;
import net.erickcaron.mybudgetapi.exceptions.IncorrectResourceInputException;
import net.erickcaron.mybudgetapi.models.Expense;
import net.erickcaron.mybudgetapi.services.CurrencyService;
import net.erickcaron.mybudgetapi.services.ExpenseService;
import net.erickcaron.mybudgetapi.services.ShopService;
import net.erickcaron.mybudgetapi.utils.Checkings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.util.List;
#RestController
public class ExpenseController {
#Autowired
private ExpenseService expenseService;
#Autowired
private ShopService shopService;
#Autowired
private CurrencyService currencyService;
#PostMapping("shops/{shopId}/currencies/{currencyId}/expenses")
#ResponseStatus(code = HttpStatus.CREATED)
public Long create(#PathVariable("shopId") Long shopId, #PathVariable("currencyId") Long currencyId, #RequestBody Expense expense) {
Checkings.checkFoundShop(shopService.findById(shopId));
Checkings.checkFoundCurrency(currencyService.findById(currencyId));
checkExpenseCalculation(expense);
return expenseService.create(shopId, currencyId, expense);
}
#GetMapping("/expenses")
public List<Expense> findAll() {
return expenseService.findAll();
}
#GetMapping("/shops/{shopId}/expenses")
public List<Expense> findAllByShopId(#PathVariable("shopId") Long shopId) {
Checkings.checkFoundShop(shopService.findById(shopId));
return expenseService.findAllByShopId(shopId);
}
#GetMapping("/expenses/{id}")
public Expense findById(#PathVariable("id") Long id) {
Checkings.checkFoundExpense(expenseService.findById(id));
return expenseService.findById(id).get();
}
#DeleteMapping("/expenses/{id}")
#ResponseStatus(code = HttpStatus.OK)
public void delete(#PathVariable("id") Long id) {
Checkings.checkFoundExpense(expenseService.findById(id));
expenseService.delete(expenseService.findById(id).get());
}
#PutMapping("/shops/{shopId}/expenses/{id}")
#ResponseStatus(code = HttpStatus.OK)
public void update(#PathVariable("shopId") Long shopId, #PathVariable("id") Long id, #RequestBody Expense expense) {
Checkings.checkFoundExpense(expenseService.findById(id));
Checkings.checkFoundShop(shopService.findById(shopId));
checkExpenseCalculation(expense);
expenseService.update(id, expense);
}
#PatchMapping("/expenses/{id}")
#ResponseStatus(code = HttpStatus.OK)
public void partialUpdate(#PathVariable("id") Long id, #RequestBody Expense expense) {
Checkings.checkFoundExpense(expenseService.findById(id));
checkExpenseCalculation(expense);
Expense expenseToUpdate = expenseService.instancingExpense(expenseService.findById(id).get(), expense);
expenseService.update(id, expenseToUpdate);
}
private void checkExpenseCalculation(Expense expense) {
if (!expenseService.checkIfExpenseCalculationIsCorrect(expense)) {
throw new IncorrectResourceInputException();
}
}
}
Based on the Expense entity you posted, your Shop and/or Currency entities probably have List *-to-many associations that you try to fetch with FetchType.EAGER. Based on this root entity, all associations reachable through some kind of EAGER attribute are considered and it is an error if there is more than 1 List *-to-many association.
What you can do is simply switch to the more logical type Set which doesn't have that problem or use LAZY which you probably also should do. You can control fetching in a fine grained manner by using entity graphs or join fetch in HQL queries.
I'm using spring-data -mongodb to do crud operations (create , read , update , delete), but
the delete function doesn't work and I don't know why? . Here is my code.
import org.bson.types.Binary;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
#ToString
#Getter
#Setter
#Document(collection = "patterns")
public class Pattern {
#Id
#Field
private String id;
#Field
#Indexed(unique = true)
private String name;
#Field
private String status;
#Field
private Binary patternFile;
}
import java.util.List;
import org.springframework.data.mongodb.repository.MongoRepository;
import fod.pfe7.administratorportal.domain.Pattern;
public interface PatternRepository extends MongoRepository<Pattern, String> {
List<Pattern> findByName(String name);
}
in my controller I do .
patternRepository.findByName(patternName).forEach(pattern -> {
patternRepository.deleteById(pattern.getId());
result.put("status", 0);
result.put("message", "pattern deleted successfuly");
});
Your data might have been created by other system, which use different presentation of "_id" (says String). With newer version of spring data mongodb, you can use #MongoId instead of #Id to control this.
In my case, the later deletes record correctly.
#Id private String id; produces
Remove using query: { "_id" : { "$oid" : "60ed51ce597826297941ade4"}} in collection: sample.
#MongoId(FieldType.STRING) private String id; produces
Remove using query: { "_id" : "60ed51ce597826297941ade4"} in collection: sample.
Maybe a little late...
I got the same error and saw that the delete query did not include the Object_Id, so the findByID method was not working either.
The solution for me was to include the targetType in the #Field annotation:
#Field(name = "_id", targetType = FieldType.OBJECT_ID)
So basically I have the following need. A person is going to POST an entity called "Expense Report" through a Rest Controller.
That entity has a field Country that is actually an association with another entity.
#EqualsAndHashCode(callSuper = true)
#Entity
#Table(name = "EXPENSE_REPORTS")
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class ExpenseReport extends BaseEntity {
#Column(name = "TRIP_DESCRIPTION", nullable = false)
private String tripDescription;
#Column(name = "JUSTIFICATION")
private String justification;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "COUNTRY_ID")
private Country country;
#Column(name = "TRIP_START_DATE")
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
#JsonDeserialize(using = LocalDateDeserializer.class)
#JsonSerialize(using = LocalDateSerializer.class)
private LocalDate tripStartDate;
#Column(name = "TRIP_END_DATE")
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
#JsonDeserialize(using = LocalDateDeserializer.class)
#JsonSerialize(using = LocalDateSerializer.class)
private LocalDate tripEndDate;
#Column(name = "TOTAL_AMOUNT")
private BigDecimal totalAmount;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name="USER_ID")
private User user;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name="EXPENSE_ITEM_ID")
private Set<ExpenseItem> expenses;
}
#EqualsAndHashCode(callSuper = true)
#Entity
#Table(name = "COUNTRIES")
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class Country extends BaseEntity {
#Column(name = "NAME")
#NaturalId
private String name;
}
What I wanted is to check, if it is possible that the caller just sends the natural id of the country (in this case, the name) and JPA rather than trying to insert this country, he finds the country with the same name and associates it.
So, rather than sending this post:
{
"tripDescription": "Some trip description...",
"justification": "Some justification...",
"country": {
"id": 12345
}
}
He sends this:
{
"tripDescription": "Some trip description...",
"justification": "Some justification...",
"country": {
"name": "BR"
}
}
I can imagine how to do this either in a repository implementation, but I was wondering if there is something automatic to do this in JPA.
Thanks in advance.
As far as I could see, there is no automatic JPA way of doing this. I know that is not much of an answer, but I also do not think it is likely that there will be a way to do this automatically in JPA (for now), since Hibernate is very focussed on using primary keys and foreign keys (i.e. surrogate IDs).
As "curiousdev" mentions in the comments, using a country code like "BR" (which can be non null unique, just like a primary key) as the key (or joincolumn) is a good alternative in this case. There is a whole discussion about it here if you are interested.
For my own interest, I did some digging in how a repository implementation could look when using the surrogate ID and natural ID. The combination of second level cache and a reference lookup looks promising. You can avoid an extra select-query in that case. The code below is runnable (with the required depencencies in place) and shows what I found so far.
The reference I'm talking about is in the line s.byNaturalId(Country.class).using("code", "NL").getReference();.
The cache is in the settings (hibernate.cache.use_second_level_cache and hibernate.cache.region.factory_class) and the annotations #org.hibernate.annotations.Cache and #NaturalIdCache.
// package naturalid;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import org.ehcache.jsr107.EhcacheCachingProvider;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.NaturalId;
import org.hibernate.annotations.NaturalIdCache;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cache.jcache.internal.JCacheRegionFactory;
import org.hibernate.dialect.H2Dialect;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
/**
* Using natural ID to relate to an existing record.
* <br>https://stackoverflow.com/questions/60475400/select-entity-with-the-informed-natural-id-rather-than-trying-to-insert-jpa-an
* <br>Dependencies:<pre>
* org.hibernate:hibernate-core:5.4.12.Final
* org.hibernate:hibernate-jcache:5.4.12.Final
* org.ehcache:ehcache:3.8.1
* com.h2database:h2:1.4.200
* org.slf4j:slf4j-api:1.7.25
* ch.qos.logback:logback-classic:1.2.3
* org.projectlombok:lombok:1.18.4
* </pre>
*/
#Slf4j
public class NaturalIdRel {
public static void main(String[] args) {
try {
new NaturalIdRel().test();
} catch (Exception e) {
log.error("Tranactions failed.", e);
}
}
void test() throws Exception {
// https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#bootstrap
Map<String, Object> settings = new HashMap<>();
// https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#configurations
settings.put("hibernate.dialect", H2Dialect.class.getName());
settings.put("hibernate.connection.url", "jdbc:h2:mem:test;database_to_upper=false;trace_level_system_out=2");
settings.put("hibernate.connection.username", "SA");
settings.put("hibernate.connection.password", "");
settings.put("hibernate.hbm2ddl.auto", "create");
settings.put("hibernate.show_sql", "true");
settings.put("hibernate.cache.use_second_level_cache", "true");
settings.put("hibernate.cache.region.factory_class", JCacheRegionFactory.class.getName());
settings.put("hibernate.cache.ehcache.missing_cache_strategy", "create");
settings.put("hibernate.javax.cache.provider", EhcacheCachingProvider.class.getName());
settings.put("hibernate.javax.cache.missing_cache_strategy", "create");
//settings.put("", "");
StandardServiceRegistry ssr = new StandardServiceRegistryBuilder()
.applySettings(settings)
.build();
Metadata md = new MetadataSources(ssr)
.addAnnotatedClass(ExpenseReport.class)
.addAnnotatedClass(Country.class)
.buildMetadata();
SessionFactory sf = md.getSessionFactoryBuilder()
.build();
try {
createCountry(sf);
createExpense(sf);
} finally {
sf.close();
}
}
void createCountry(SessionFactory sf) {
Country c = new Country();
c.setCode("NL");
try (Session s = sf.openSession()) {
save(s, c);
}
}
void createExpense(SessionFactory sf) {
ExpenseReport er = new ExpenseReport();
er.setDescription("Expenses");
er.setReason("Fun");
// Watch (log) output, there should be no select for Country.
try (Session s = sf.openSession()) {
// https://www.javacodegeeks.com/2013/10/natural-ids-in-hibernate.html
Country cer = s.byNaturalId(Country.class).using("code", "NL").getReference();
er.setCountry(cer);
save(s, er);
}
}
void save(Session s, Object o) {
Transaction t = s.beginTransaction();
try {
s.save(o);
t.commit();
} finally {
if (t.isActive()) {
t.rollback();
}
}
}
#Entity
#Data
static class ExpenseReport {
#Id
#GeneratedValue
int id;
#Column
String description;
#Column
String reason;
#ManyToOne
// Can also directly map country code.
// https://stackoverflow.com/questions/63090/surrogate-vs-natural-business-keys
Country country;
}
#Entity
#Data
// https://vladmihalcea.com/the-best-way-to-map-a-naturalid-business-key-with-jpa-and-hibernate/
#org.hibernate.annotations.Cache(
usage = CacheConcurrencyStrategy.READ_WRITE
)
#NaturalIdCache
static class Country {
#Id
#GeneratedValue
int id;
#NaturalId
String code;
}
}
Use association like below, instead of COUNTRIES.COUNTRY_ID use COUNTRIES.NAME
public class ExpenseReport extends BaseEntity {
#Column(name = "TRIP_DESCRIPTION", nullable = false)
private String tripDescription;
#Column(name = "JUSTIFICATION")
private String justification;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = “NAME”,referencedColumnName = "name" ) //Do not use COUNTRY_ID
private Country country;
….
}
public class Country extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "NAME”,nullable = false, updatable = false, unique = true)
#NaturalId(mutable = false)
private String name;
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Country)) {
return false;
}
Product naturalIdCountry = (Country) o;
return Objects.equals(getName(), naturalIdCountry.getName());
}
#Override
public int hashCode() {
return Objects.hash(getName());
}
}
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 am getting an error - 'Class com.fasterxml.jackson.datatype.joda.deser.DateTimeDeserializer has no default (no arg) constructor' while I am trying to call restangular for post request. When I call the method it goes in the error block.
Restangular.all('tests').post($scope.test).then(function (data) {
$scope.test.id = data.id;
$location.path($location.path() + data.id).replace();
}, function (error) {
$scope.exceptionDetails = validationMapper(error);
});
I am using jackson-datatype-joda - 2.6.5
The entity class used in this method as follows -
#Data
#JsonInclude(JsonInclude.Include.NON_NULL)
#Entity
#Table(name = "Test")
#EqualsAndHashCode(of = "id", callSuper = false)
#ToString(exclude = {"keywords", "relevantObjectIds"})
public class Test {
#Id
#Column(unique = true, length = 36)
private String id;
#NotBlank
#NotNull
private String name;
#Transient
private List<Testabc> Testabcs = new ArrayList<>();
}
The entity class used in above entity Testabc class as follows
#Data
#JsonInclude(JsonInclude.Include.NON_NULL)
#Slf4j
#Entity
#Table(name = "Test_abc")
#EqualsAndHashCode(of = "id", callSuper = false)
public class Testabc{
#Id
#Column(unique = true, length = 36)
#NotNull
private String id = UUID.randomUUID().toString();
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime")
#JsonDeserialize(using = DateTimeDeserializer.class)
#JsonSerialize(using = DateTimeSerializer.class)
private DateTime createdOn;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "Id")
#NotNull
private t1 pid;
private long originalSize;
}
Finally resource class where I am requesting to create test data -
#ApiOperation(value = "Create new Test", notes = "Create a new Test and return with its unique id", response = Test.class)
#POST
#Timed
public Test create(Test newInstance) {
return super.create(newInstance);
}
I have tried to add this
#JsonIgnoreProperties(ignoreUnknown = true) annotation on entity class, but it doesn't work.
Can anyone help to resolve this problem?
Looking at the latest sources of DateTimeDeserializer you can easily see that it does not have a no-arg constructor, which seems to be required by the framework. This is also indicated in both the linked questions: joda.time.DateTime deserialization error & Jackson, Retrofit, JodaTime deserialization
Since you want to use only an annotation based solution, a possible workaround would be to create your own deserializer which extends the DateTimeDeserializer and provides a nor-arg constructor.
1) MyDateTimeSerializer
import com.fasterxml.jackson.datatype.joda.cfg.FormatConfig;
import com.fasterxml.jackson.datatype.joda.deser.DateTimeDeserializer;
import org.joda.time.DateTime;
public class MyDateTimeDeserializer extends DateTimeDeserializer {
public MyDateTimeDeserializer() {
// no arg constructor providing default values for super call
super(DateTime.class, FormatConfig.DEFAULT_DATETIME_PARSER);
}
}
2) AClass using the custom deserializer
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.joda.ser.DateTimeSerializer;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
public class AClass {
#JsonSerialize(using = DateTimeSerializer.class) // old serializer
#JsonDeserialize(using = MyDateTimeDeserializer.class) // new deserializer
private DateTime createdOn = DateTime.now(DateTimeZone.UTC); // some dummy data for the sake of brevity
public DateTime getCreatedOn() {
return createdOn;
}
public void setCreatedOn(DateTime createdOn) {
this.createdOn = createdOn;
}
}
3) Unit test
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
public class ATest {
#Test
public void testSomeMethod() throws Exception {
// Jackson object mapper to test serialization / deserialization
ObjectMapper objectMapper = new ObjectMapper();
// our object
AClass initialObject = new AClass();
// serialize it
String serializedObject = objectMapper.writeValueAsString(initialObject);
// deserialize it
AClass deserializedObject = objectMapper.readValue(serializedObject, AClass.class);
// check that the dates are equal (no equals implementation on the class itself...)
assertThat(deserializedObject.getCreatedOn(), is(equalTo(initialObject.getCreatedOn())));
}
}
This deserializer was never meant to be used by annotations; and as others have mentioned, cannot. In general you really should just add/register JodaModule and serializer/deserializer gets added as expected.
I am not sure why you would not want to go that route; it might be worth expanding on why this solution
(or registering deserializer you get by DateTimeDeserializer.forType(ReadableDateTime.class) through your custom module)
is not applicable.