Small question for Spring Kafka and OpenAPI V3 please.
In the paste, we had a Spring Web MVC web app, very straightforward, type https://interviewnoodle.com/documenting-a-springboot-rest-api-with-openapi-3-7b2bc1605f
package com.openapi.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
#Schema(description = "Book object")
#Entity
#Table(name="books")
public class Book {
#JsonProperty(value="id", required=true, index = 10)
#Schema(description = "Unique identifier of the Book.",
example = "1", required = true)
private long id;
#JsonProperty(value="title", required=true, index = 20)
#Schema(description = "Name of the title.",
example = "Java", required = true)
#NotBlank
#Size(min = 0, max = 20)
private String title;
#JsonProperty(value="author", required=true, index = 30)
#Schema(description = "Name of the author.",
example = "Max Abi", required = true)
#NotBlank
#Size(min = 0, max = 30)
private String author;
public Book() {}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
#Column(name = "title", nullable = false)
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
#Column(name = "author", nullable = false)
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
package com.openapi.controller;
import com.openapi.model.Book;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.Collection;
#Tag(name = "book", description = "the book API")
#RequestMapping("/api/v1/books")
public interface BookApi {
#Operation(summary = "Create book", description = "This can only be done by the logged in book.", tags = { "book" })
#ApiResponses(value = { #ApiResponse(description = "successful operation", content = { #Content(mediaType = "application/json", schema = #Schema(implementation = Book.class)), #Content(mediaType = "application/xml", schema = #Schema(implementation = Book.class)) }) })
#PostMapping(value = "/", consumes = { "application/json", "application/xml", "application/x-www-form-urlencoded" })
#ResponseStatus(HttpStatus.CREATED)
public ResponseEntity<Book> postBook(
#NotNull
#Parameter(description = "Created book object", required = true)
#Valid #RequestBody Book body,
#NotNull #Parameter(description = "select which kind of data to fetch", required = true)
#Valid #RequestHeader(value="bookAuthorization", required = true) String bookAuthorization)
throws Exception;
}
For each and every client applications wanting to interact with us, i.e sending the http request with a Book object, we first jumped into a call, wrote a long mail explaning what the Book looks like.
Then, we discovered <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-ui</artifactId> which changed the game.
We would just do the annotations, generate the Open API v3 doc as we build our project, no extra step.
We would then just send the spec.yml via email, and tell them "please go to http://the-cool-host:8080/v3/api-docs/, and you will be able to see what you should be sending us".
This worked like a charm.
For organizational reason, we changed the http based application to a Kafka based approach.
With that, we lost ability to generate the doc, and also, host it on a website.
What I tried:
I tried leaving the annotations, and running the maven clean install command to generate the doc, but it is not generating anymore.
May I ask what did I miss please?
What are the possibilities to generate the Open API v3 doc on the go, and host it, but for Spring Kafka please?
Thank you
Related
This question already has answers here:
How to fix org.hibernate.LazyInitializationException - could not initialize proxy - no Session
(24 answers)
Closed last year.
I'm trying to make a database based API I have a huge load of data but every time I try to bring the data even with pageable property I get this error:
{
"timestamp": "2022-01-12T01:34:01.851+00:00",
"status": 500,
"error": "Internal Server Error",
"trace": "org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: br.com.leomanzini.space.flight.news.model.Article.launches, could not initialize proxy - no Session\r\n\tat org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:612)\r\n\tat org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:218)\r\n\tat org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:591)\r\n\tat org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:149)\r\n\tat org.hibernate.collection.internal.PersistentBag.iterator(PersistentBag.java:387)\r\n\tat java.base/java.lang.Iterable.forEach(Iterable.java:74)\r\n\tat br.com.leomanzini.space.flight.news.dto.ArticlesDTO.<init>(ArticlesDTO.java:65)\r\n\tat br.com.leomanzini.space.flight.news.service.ArticleService.lambda$findAll$0(ArticleService.java:30)\r\n\tat java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)\r\n\tat java.base/java.util.ArrayList$Itr.forEachRemaining(ArrayList.java:1032)\r\n\tat java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)\r\n\tat java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)\r\n\tat java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)\r\n\tat java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)\r\n\tat java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)\r\n\tat java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)\r\n\tat org.springframework.data.domain.Chunk.getConvertedContent(Chunk.java:173)\r\n\tat org.springframework.data.domain.PageImpl.map(PageImpl.java:106)\r\n\tat br.com.leomanzini.space.flight.news.service.ArticleService.findAll(ArticleService.java:30)\r\n\tat br.com.leomanzini.space.flight.news.controller.SpaceFlightsApiController.findAll(SpaceFlightsApiController.java:28)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\r\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:566)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:655)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:764)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:895)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1732)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.base/java.lang.Thread.run(Thread.java:834)\r\n",
"message": "failed to lazily initialize a collection of role: br.com.leomanzini.space.flight.news.model.Article.launches, could not initialize proxy - no Session",
"path": "/articles"
}
At the database I have an amount of 11795 articles and their respectives relationship with others tables, there is a way to optimize this load or just make it works?
Article entity:
import lombok.*;
import javax.persistence.*;
import java.util.List;
#Entity
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "article")
public class Article {
#Id
#EqualsAndHashCode.Include
private Long id;
#Column(nullable = false)
private Boolean featured;
#Column(nullable = false)
private String title;
#Column(nullable = false)
private String url;
#Column(name = "image_url", nullable = false)
private String imageUrl;
#Column(name = "news_site", nullable = false)
private String newsSite;
#Column(nullable = false)
private String summary;
#Column(name = "published_at", nullable = false)
private String publishedAt;
#ManyToMany(fetch = FetchType.LAZY)
private List<Launches> launches;
#ManyToMany(fetch = FetchType.LAZY)
private List<Events> events;
#Column(name = "inserted_by_human")
private Boolean insertedByHuman = false;
}
Events and Launches entities:
import lombok.*;
import javax.persistence.Entity;
import javax.persistence.Id;
#Entity
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class Events {
#Id
#EqualsAndHashCode.Include
private Long id;
private String provider;
}
import lombok.*;
import javax.persistence.Entity;
import javax.persistence.Id;
#Entity
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class Launches {
#Id
#EqualsAndHashCode.Include
private String id;
private String provider;
}
The repository is just a interface with JpaRepository and the Article as entity.
The service class:
import br.com.leomanzini.space.flight.news.dto.ArticlesDTO;
import br.com.leomanzini.space.flight.news.model.Article;
import br.com.leomanzini.space.flight.news.repository.ArticleRepository;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
#Service
#AllArgsConstructor
public class ArticleService {
#Autowired
private final ArticleRepository articleRepository;
public Page<ArticlesDTO> findAll(Pageable pageable) {
Page<Article> articleList = articleRepository.findAll(pageable);
return articleList.map(article -> new ArticlesDTO(article));
}
}
If I just use my entity as a return, the methods work, but when I convert the same to dto the application breaks. The dto class is below:
import br.com.leomanzini.space.flight.news.model.Article;
import lombok.*;
import javax.management.ConstructorParameters;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class ArticlesDTO implements Serializable {
#NotEmpty
#EqualsAndHashCode.Include
private Long id;
#NotEmpty
private String title;
#NotEmpty
private String url;
#NotEmpty
private String imageUrl;
#NotEmpty
private String newsSite;
#NotEmpty
private String summary;
#NotEmpty
private String publishedAt;
#NotEmpty
private String updatedAt;
#NotEmpty
private Boolean featured;
#Valid
private List<LaunchesDTO> launches;
#Valid
private List<EventsDTO> events;
public ArticlesDTO (Article article) {
id = article.getId();
title = article.getTitle();
url = article.getUrl();
imageUrl = article.getImageUrl();
newsSite = article.getNewsSite();
summary = article.getSummary();
publishedAt = article.getPublishedAt();
updatedAt = article.getPublishedAt();
featured = article.getFeatured();
launches = new ArrayList<>();
article.getLaunches().forEach(launch -> {
LaunchesDTO launchesDTO = new LaunchesDTO(launch.getId(), launch.getProvider());
launches.add(launchesDTO);
});
events = new ArrayList<>();
article.getEvents().forEach(event -> {
EventsDTO eventsDTO = new EventsDTO(event.getId(), event.getProvider());
events.add(eventsDTO);
});
}
}
There is anyway to fix the error and make it works? Something to accelerate the database load or something else?
With FetchType.LAZY, the query that is used for retrieving those fields will be executed only when it is accessed for the first time. In order to do this, Hibernate creates and configures proxy classes for our entity classes (that's why we also should not declare our entity classes to be final if we want to use the lazy load feature).
When Hibernate initializes proxies, it requires a Session in order to do the task. That's why when you don't add the #Transactional annotation to the service method, it will throw out an LazyInitializationException...no Session.
Beside the solution above, if you don't want to add the #Transactional annotation, you can set hibernate.enable_lazy_load_no_trans property to true.
<property
name="hibernate.enable_lazy_load_no_trans"
value="true"/>
But this solution is considered to be an anti-pattern. It will create a new Session everytime you try to lazy-load. You can read it more here. The hibernate.enable_lazy_load_no_trans Anti-Pattern
The proposed answer was that in the service class, the #Transactional(readOnly = true) attribute has been enabled, it works fine to resolve the error
failed to lazyly initialize a collection of role: br.com .
leomanzini.space.flight.news.model.Article.launches, could not
initialize proxy.
The method at service class looked like this:
import br.com.leomanzini.space.flight.news.dto.ArticlesDTO;
import br.com.leomanzini.space.flight.news.model.Article;
import br.com.leomanzini.space.flight.news.repository.ArticleRepository;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
#Service
#AllArgsConstructor
public class ArticleService {
#Autowired
private final ArticleRepository articleRepository;
#Transactional(readOnly = true)
public Page<ArticlesDTO> findAll(Pageable pageable) {
Page<Article> articleList = articleRepository.findAll(pageable);
return articleList.map(article -> new ArticlesDTO(article));
}
}
And the requisition working fine!
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());
}
}
My variable names in entity class are assignTo and assignBy, The column name of the above in MySQL db are assignto and assignby respectively.
But project is creating it by name "assignmentTo" and "assignmentBy".
I have drop the whole databases and re-created with mvn clean install after deleting .m2/repository folder.
Postman is still returning "assignmentTo" and "assignmentBy" on GET API.
Called POST method with "assignTo" and "assignBy" names, still got "assignmentTo" and "assignmentBy".
Class LeadAssignment:
#Entity
#Table(name = "lead_assignment")
public class LeadAssignment {
#Id
#GeneratedValue(strategy = javax.persistence.GenerationType.IDENTITY )
#Column(name = "laid", nullable = false, updatable = false)
private Long id;
#Column(name = "first_name", nullable = false)
private String firstname;
#Column(name = "last_name", nullable = false)
private String lastname;
#Column(name = "assignto" , nullable = false)
private String assignTo;
#Column(name = "assignby", nullable = false)
private String assignBy;
#Column(name = "requirement" , nullable = false)
private String requirements;
#Column(name = "remark" , nullable = false)
private String remarks;
Controller class LeadAssignmentController :
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping(value = "/leadassignment")
public class LeadAssignmentController {
//#Autowired
private LeadAssignmentDao leadAssignmentDao;
LeadAssignmentController(LeadAssignmentDao leadAssignmentDao){
this.leadAssignmentDao = leadAssignmentDao;
}
#GetMapping("/getall")
List<LeadAssignment> getLeadAssignmentList() {
System.out.println("inside lead adsdignment conntroller get mapping");
return leadAssignmentDao.findAll();
}
#GetMapping("/get/{id}")
Optional<LeadAssignment> getLeadAssignment(#PathVariable Long id) {
return leadAssignmentDao.findById(id);
}
#GetMapping("/get/assignto/{assignTo}")
LeadAssignment getLeadAssignmentAssignTo(#PathVariable String assignTo, #RequestParam Map<String, String> params){
System.out.println("Inside start of lead assignment assign to");
System.out.println(params);
LeadAssignment result = leadAssignmentDao.findByAssignTo(assignTo);
return result;
//System.out.println("Inside end of get sales email");
}
#GetMapping("/get/assignby/{assignBy}")
LeadAssignment getLeadAssignmentAssignBy(#PathVariable String assignBy, #RequestParam Map<String, String> params){
System.out.println("Inside start of lead assignment by");
System.out.println(params);
LeadAssignment result = leadAssignmentDao.findByAssignBy(assignBy);
return result;
//System.out.println("Inside end of get sales email");
}
#DeleteMapping("/delete/{id}")
public boolean deleteLeadAssignment(#PathVariable Long id) {
leadAssignmentDao.deleteById(id);
return true;
}
#PutMapping("/update/{id}")
public LeadAssignment updateLeadAssignment(#RequestBody LeadAssignment leadAssignment, #PathVariable Long id) {
System.out.println("Inside lead assignmet update method");
Optional<LeadAssignment> found = leadAssignmentDao.findById(id);
//if(!found.isPresent())
leadAssignment.setId(id);
leadAssignmentDao.save(leadAssignment);
return found.get();
}
/*
#PutMapping("/update/email/{email}")
public LeadAssignment updateLeadAssignmentEmail(#RequestBody User user, #PathVariable String email ) {
System.out.println("inside user email PUT method");
User emailfind = userDao.findByEmail(email);
user.setEmail(email);
userDao.save(user);
return emailfind;
}
*/
/*
#PutMapping("/update/{id}")
public User updateUser(#RequestBody User user) {
return userDao.save(user);
}
*/
#PostMapping("/create")
public LeadAssignment createLeadAssignment(#RequestBody LeadAssignment leadAssignment) {
return leadAssignmentDao.save(leadAssignment);
}
}
My application.properties file :
# ===============================
# = DATA SOURCE
# ===============================
# Set here configurations for the database connection
# Connection url for the database "netgloo_blog"
#spring.datasource.url=jdbc:mysql://localhost:3306/lmsAngularSpring?createDatabaseIfNotExist=true
spring.datasource.url=jdbc:mysql://localhost:3306/lms
# Username and secret
spring.datasource.username=root
spring.datasource.password=test
# Keep the connection alive if idle for a long time (needed in production)
spring.datasource.testWhileIdle=true
spring.datasource.validationQuery=SELECT 1
# ===============================
# = JPA / HIBERNATE
# ===============================
# Use spring.jpa.properties.* for Hibernate native properties (the prefix is
# stripped before adding them to the entity manager).
# Show or not log for each sql query
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.current_session_context_class = org.springframework.orm.hibernate5.SpringSessionContext
# Hibernate ddl auto (create, create-drop, update): with "update" the database
# schema will be automatically updated accordingly to java entities found in
# the project
#line below was earlier un-commented
#spring.jpa.hibernate.ddl-auto=update
# Allows Hibernate to generate SQL optimized for a particular DBMS
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
spring.jpa.open-in-view=false
#spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.DefaultNamingStrategy
Expected postman output:
{
"id": 1,
"remarks": "demo3",
"demanderRentLeadStatus": null,
"demanderBuyLeadStatus": null,
"supplierSellLeadStatus": null,
"supplierRentLeadStatus": null,
"sales": null,
"lastname": "foo",
"firstname": "bar",
"assignTo": "david",
"assignBy": "james",
"requirements": "for rent out"
}
Actual output for postman:
{
"id": 1,
"remarks": "demo3",
"demanderRentLeadStatus": null,
"demanderBuyLeadStatus": null,
"supplierSellLeadStatus": null,
"supplierRentLeadStatus": null,
"sales": null,
"lastName": null,
"firstName": null,
"assignmentTo": null,
"assignmentBy": null,
"requirments": null
}
Rename getter and setter of your class. Make it like:
getter and setter for assignTo are getAssignTo() and setAssignTo(String s) respectively. and do same for assignBy.
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.
Hi i am following an example I found
http://www.mkyong.com/spring-mvc/spring-3-mvc-and-jsr303-valid-example/
The problem is that no errors are found in my profile that I post. I should be. Why can this happend?
#Test
#Ignore
public void anotherTest() {
Profile profile = ProfileUtil.getProfile();
profile.setEmail("user#mail.com");
profile.setSex("dafjsgkkdsfa");
BindingResult bindingResult = new BeanPropertyBindingResult(profile, "profile");
userController.postUser(new ModelMap(), profile, bindingResult);
if (bindingResult.hasErrors()) {
System.out.println("errors");
}
assertTrue(bindingResult.hasErrors());
profileService.deleteProfile(profile);
}
#RequestMapping(value = "/", method = RequestMethod.POST)
public View postUser(ModelMap data, #Valid Profile profile, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
System.out.println("No errors");
return dummyDataView;
}
data.put(DummyDataView.DATA_TO_SEND, "users/user-1.json");
profileService.save(profile);
return dummyDataView;
}
Edit:
This is the Profile. I am testing the sex now so I guess thats what is important.
package no.tine.web.tinetips.domain;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import no.tine.web.tinetips.util.CommonRegularExpressions;
import org.hibernate.validator.constraints.NotBlank;
#Entity
public class Profile {
#Id
#GeneratedValue
private Long id;
#NotNull(message = "profile.email.null")
#NotBlank(message = "profile.email.blank")
#Size(max = 60, message = "profile.email.maxlength")
#Pattern(regexp = CommonRegularExpressions.EMAIL, message = "profile.email.regex")
#Column(name = "Email", unique = true)
private String email;
#Pattern(regexp = "^[M|F]{1}$", message = "profile.sex.regex")
#Size(max = 1, message = "profile.sex.maxlength")
private String sex;
}
Basically you instantiated a POJO with this.userController = new UserController(), then called its method this.controller.postUser(...). Just simple Java with a simple object, without any relation to Spring and Spring MVC : #Valid is not taken into account.
If you want to make it work, you will have to give your test class some Spring information, with #RunWith(SpringJUnit4ClassRunner.class) and #ContextConfiguration(...). Then, for the Spring MVC part, you will have to mock a request call on your Controller through some Spring MVC facilites. It is done differently if you use Spring MVC 3.0- or 3.1+. For more information and actual code, see this post and its answers, for example.