Performance issue in Spring Boot Java app with SQL Server - java

I am facing performance issues with a Spring Boot Java App which gets data from a SQL Server database. A simple query like:
SELECT a, b, SUM(c)
FROM table
WHERE date = '2023-02-01' AND year = 2023
GROUP BY a, b
is executed. The result of the query is 12 rows. When I execute this query in SSMS, the result is there immediately. If the query gets executed via the Spring Boot app in a browser window or with Postman, it takes randomly 3.5 to 10 seconds to get the result.
The table contains roughly 8 million rows and 14 columns:
1 PK INT column
1 DATE column
2 DECIMAL(12,2)
10 NVARCHAR(x)
The query in the Spring Boot app is written in Native SQL annotated with #Query. All my colleagues use ASP.NET as back-end, and they are taking the same SQL Server. Their performance is great. It looks like there are configuration issues.
What I have tried so far:
I added sendParametersAsUnicode=false to application.properties
I copied the entire table: instead of NVARCHAR(x) I took VARCHAR(x) for all string-related columns.
I tracked the execution time with Postman: >99% of execution time falls in the category transfer time.
I tried to annotate NVARCHAR(x) columns to annotate with #Nationalized
I read about this: https://vladmihalcea.com/sql-server-jdbc-sendstringparametersasunicode/ to understand the difference between NVARCHAR(x) and VARCHAR(x) in terms of Spring Boot and SQL Server.
Any ideas what might be the game changer to decrease execution time from multiple seconds to far less than a second?
Here is more code of my project:
application.properties:
spring.datasource.url=jdbc:sqlserver://10.191.144.180:1433;database=Spring;encrypt=true;trustServerCertificate=true;sendStringParametersAsUnicode=false
spring.datasource.username=username
spring.datasource.password=pw123456
spring.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring.jpa.show-sql=true
spring.jpa.hibernate.dialect=org.hibernate.dialect.SQLServer2012Dialect
spring.jpa.open-in-view=false
Table Class:
#Entity
#Table(name="Fact_Snapshots_Agg")
public class FSAGG {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
Date filedate;
String jahr;
String a;
String b;
String d;
String e;
String f;
String g;
String h;
float c;
float i;
String j;
String k;
}
plus constructors and getters and setters
Resource File:
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.analytics_test.model.FSAGGTInterface;
import com.analytics_test.service.FSAGGService;
#RestController
#CrossOrigin
#RequestMapping("/FSAGG")
public class FSAGGResource {
private final FSAGGService fsaggService;
#Autowired
public FSAGGResource(FSAGGService fsaggService) { this.fsaggService = fsaggService; }
#GetMapping("/Actuals/Total/{jahr}/gesamt")
public List<FSAGGTInterface> getActualsTotalGesamt(#PathVariable("jahr") String jahr) { return fsaggService.getActualsTotalGesamt(jahr); }
}
Repository File:
package com.analytics_test.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import com.analytics_test.model.FSAGG;
import com.analytics_test.model.FSAGGTInterface;
public interface FSAGGRepository extends JpaRepository<FSAGG, Long> {
#Query(value = "a as a, b as b, SELECT SUM(c) as c "
+ "FROM FSAGG WHERE filedate = '2023-02-01' AND year = :year "
+ "GROUP BY a, b"
)
List<FSAGGTInterface> getActualsTotalGesamt(#Param("year") String year);
}
Service Class:
package com.analytics_test.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import com.analytics_test.model.FSAGGTInterface;
import com.analytics_test.repository.FSAGGRepository;
#Component
#Service
public class FSAGGService {
private final FSAGGRepository fsaggRepository;
#Autowired
public FSAGGService(FSAGGRepository fsaggRepository) { this.fsaggRepository = fsaggRepository; }
public List<FSAGGTInterface> getStuff() { return fsaggRepository.getStuff(); }
public List<FSAGGTInterface> getActualsTotalGesamt(String jahr) { return fsaggRepository.getActualsTotalGesamt(jahr); }
}

Rewrite the service method getActualsTotalGesamt to confirm that the slow query is the issue rather than something else in spring boot.
public List<FSAGGTInterface> getActualsTotalGesamt(String jahr) {
Date start = new Date();
List<FSAGGTInterface> retVal = fsaggRepository.getActualsTotalGesamt(jahr);
System.out.println(“repo call duration ms: “ + ((new Date()).getTime() - start.getTime()));
return retVal;
}

Related

How can I get a property from an entity table class in Java Spring Boot?

I'm wondering how can I access an attribute from my entity model class to perform a condition. I need to validate whether a column status from the database is 0 or 2 before the deleteById() repository function executes the deletion, as if it is 1, it shouldn't be executed.
My thinking is to put the business login in the service layer, so I'm trying to put the condition right there:
public void delete(int id) {
if (reportRequest.getStatus() != 1) {
log.info("The report request {} was successfully deleted", id);
reportRequestRepository.deleteById(id);
} else {
log.error("It was not possible to delete the selected report as it hasn't been processed yet");
}
}
I'm getting the following error:
Error starting ApplicationContext. To display the conditions report re-run your
application with 'debug' enabled.
2022-10-25 18:44:38.538 ERROR 2328 --- [ main]
o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.ssc.test.cb3.service.ReportRequestService required a
bean of type 'com.ssc.test.cb3.model.ReportRequest' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'com.ssc.test.cb3.model.ReportRequest' in your
configuration.
2022-10-25 18:44:38.543 ERROR 2328 --- [ main]
o.s.test.context.TestContextManager : Caught exception while allowing
TestExecutionListener
[org.springframework.test.context.web.ServletTestExecutionListener#7d446ed1] to prepare
test instance [com.ssc.test.cb3.Cb3ApplicationTests#1f72fbd1]
java.lang.IllegalStateException: Failed to load ApplicationContext
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 4.735 s <<< FAILURE! -
in com.ssc.test.cb3.Cb3ApplicationTests
contextLoads Time elapsed: 0.001 s <<< ERROR!
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error
creating bean with name 'reportRequestController': Unsatisfied dependency expressed
through field 'reportRequestService'; nested exception is
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean
with name 'reportRequestService' defined in file
At the end of the error it is the following, I attach an image as somehow I cannot paste it here:
Error tracer
No qualifying bean of type 'com.ssc.test.cb3.model.ReportRequest' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
This is my current code:
Model class ReportRequest:
package com.ssc.test.cb3.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Class that models the entity report request table of the database
* #author ssc
*/
#Entity
#Table(name = "report_request")
#Data
public class ReportRequest {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "seq_id")
private int id;
#Column(name = "request_date", insertable = false, nullable = true)
private String requestDate;
#Column(name = "request_userid")
private int requestUserId;
#Column(name = "request_email")
private String requestEmail;
#Column(name = "start_date")
private String startDate;
#Column(name = "end_date")
private String endDate;
#Column(name = "report_type")
private int reportType; // 0 === cliente, 1 === proveedor
#Column(name = "contact_id")
private int contactId;
#Column(name = "contact_id_customer") // Id from the client or provider chosen
private int contactIdCustomer;
#Column(name = "contact_id_provider") // Id from the provider or provider chosen
private int contactIdProvider;
private String rgids;
private int status; // 0 === active, 1 === inactive
#Column(name = "process_start")
private String processStart;
#Column(name = "process_finish")
private String processFinish;
#Column(name = "call_filter") // 0 === Answered calls, 1 === Not answered, 2 === both
private int callQuery;
#Column(name = "file_name")
private String fileName;
}
Repository class:
package com.ssc.test.cb3.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.ssc.test.cb3.model.ReportRequest;
/**
* Class that extends to the repository for database management queries with
* table report_request
*
* #author ssc
*/
#Repository
public interface ReportRequestRepository extends JpaRepository<ReportRequest, Integer> {
}
Service class:
package com.ssc.test.cb3.service;
import com.ssc.test.cb3.repository.ReportRequestRepository;
import org.springframework.stereotype.Service;
import com.ssc.test.cb3.model.ReportRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Class to prepare the services to be dispatched upon request.
*
* #author ssc
*/
#Service
#RequiredArgsConstructor
#Slf4j
public class ReportRequestService {
#Autowired
private ReportRequest reportRequest;
private final ReportRequestRepository reportRequestRepository;
/**
* Function to delete a report from the database
*
* #param id from the report request objet to identify what is the specific
* report
*/
// public void delete(int id) {
// log.info("The report request {} was successfully deleted", id);
// reportRequestRepository.deleteById(id);
// }
public void delete(int id) {
if (reportRequest.getStatus() != 1) {
log.info("The report request {} was successfully deleted", id);
reportRequestRepository.deleteById(id);
} else {
log.error("It was not possible to delete the selected report as it hasn't been processed yet");
}
}
}
Class controller:
package com.ssc.test.cb3.controller;
import com.ssc.test.cb3.model.ReportRequest;
import com.ssc.test.cb3.service.ReportRequestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Class to handle REST services and APIs for the download Report's class
*
* #author ssc
*/
#RestController
#RequestMapping("/v1/reportRequest")
#CrossOrigin(origins = "http://localhost:3000")
public class ReportRequestController {
#Autowired
private ReportRequestService reportRequestService;
/**
* Method to delete a report request by its id number, it will call the service method and passed the id captured
* #param id
*/
#DeleteMapping("/delete/{id}")
public void deleteReport(#PathVariable int id) {
reportRequestService.delete(id);
}
}
I would appreciate so much if someone can guide me on how to achieve this task.
You're trying to inject an entity class into the service class:
#Autowired
private ReportRequest reportRequest;
That line is throwing the exception because that entity is not managed by Spring.
I guess you´re trying to get a specific record, in that way you have to use the repository.
Try this:
public void delete(int id) {
ReportRequest reportRequest = reportRequestRepository.findById(id).orElse(null);
if (reportRequest == null || reportRequest.getStatus() == 1) {
log.error("It was not possible to delete the selected report as it hasn't
been processed yet");
} else {
reportRequestRepository.deleteById(id);
log.info("The report request {} was successfully deleted", id);
}
}
I'll also recommend you throw an exception and use #ControllerAdvice to handle the exception to be sent to front-end.
Try to add (guess may be missed) #ComponentScan in your #Configuration class / SpringBoot starter.
For Example :
#Configuration
#ComponentScan("com.my.package")
public class MyRootConfigurationClass
Despite is possible to externalize the values used to define what can be deleted or should not, as correctly pointed by an earlier answer, I assume that your logic does not require an extreme flexibility. Consequently a simpler and cleaner approach may implement an enum class for the status.
public enum Status {
STATUS_PROCESSED_TYPE_ONE(0),
STATUS_UNPROCESSED(1),
STATUS_PROCESSED_TYPE_TWO(2);
private int value;
Status(int status) {
this.value = status;
}
public int getValue() {
return value;
}
}
This class does not require any Spring "magic" and helps to externalize the specific values (0,1,2) from your logic which may be then implemented in your service class as
boolean isDeletable(int value) {
return Status.STATUS_PROCESSED_TYPE_ONE.getValue() == value
|| Status.STATUS_PROCESSED_TYPE_TWO.getValue() == value;
}

Does findById() actually load data from a JPA repository?

I am a Hibernate beginner. I did a couple of simple tutorials and am trying to write a simple shop backend. Everything works as it should, but I am seeing strange things in my unit tests. When I save an entity, then retrieve it using findById(), it seems that I am simply getting the same object I called save() on, without even retrieving actual values from the database:
package com.bo.learnjava.shop1.repository;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name = "PRODUCTS")
public class Product {
#Id
#GeneratedValue
#Column(name="ID")
long id;
#Column(name="NAME")
String name = "";
#Column(name="PRICE_CENTS")
int priceCents = 0;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPriceCents() {
return priceCents;
}
public void setPriceCents(int priceCents) {
this.priceCents = priceCents;
}
public long getId() {
return id;
}
}
package com.bo.learnjava.shop1.repository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface ProductRepository extends PagingAndSortingRepository<Product,Long> {
}
package com.bo.learnjava.shop1.repository;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
#DataJpaTest
public class ProductRepositoryTest {
#Autowired
ProductRepository repo;
#Test
void testProductRepository() {
Product p=new Product();
p.setName("Milk");
p.setPriceCents(134);
repo.save(p);
// Modify the value to check that repo.findById() actually retrieves *saved* data
p.setPriceCents(9999);
Optional<Product> productFromRepo=repo.findById(p.getId());**
// I expect productFromRepo to contain the values I called save() with
// (price == 134). But productFromRepo.get() returns exactly the same Java object
// as p (with price == 9999), so no actual data was retrieved from the database - why?
assertTrue(productFromRepo.isPresent());
System.out.println("productFromRepo.priceCents="+productFromRepo.get().getPriceCents()); // Outputs 9999!
assertEquals(134,productFromRepo.get().getPriceCents()); // THIS FAILS!!!
}
}
Why does Hibernate behave like that, and how do I test that stuff I write to the database via Hibernate actually gets retrieved back from the database?
Additionaly to comment about first level cache.
If you are extending JpaRepository you can use
repo.saveAndFlush(p);
or
repo.save(p); repo.flush();
to immediately save data in DB.
After it - repo.findById(p.getId()); will return updated data.

REST: How do I build a request path with a parameter as first token?

I am new to RESTful services. I usually develop Java EE applications and SOAP services in a JBoss / Wildfly environment. I am currently trying to find my way into RESTful services to broaden my knowledge. Since I am feeling familiar with JBoss / Wildfly I decided to go with RESTEasy.
I decided to create a RESTful service for an example pet shop chain. As a chain the pet shop has multiple stores which are identfied by a shop id (e.g. shop1, shop2, etc.). I have created multiple REST services to segment services based on technical functionality (e.g. article services => article.war, order service => orde.war, etc.
I want to create human readable URLs, like:
GET:
http://mypetshop.example/rest/{shopId}/article/{articleId}
POST with JSON formatted order content:
http://mypetshop.example/rest/{shopId}/order/create
So far I have only managed to create URLs like:
GET:
http://mypetshop.example/rest/article/{shopId}/{articleId}
POST with JSON formatted order content:
http://mypetshop.example/rest/order/create/{shopId}
Is my wanted REST path possible or do I have to keep up with my current solution?
Best regards,
CB
Here is an example code for article services:
ArticleRestApplication.java:
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
#ApplicationPath(ArticleRestApplication.ROOT_PATH)
public class OrderRestApplication extends Application {
public static final String ROOT_PATH = "/article";
}
ArticleService.java
public interface ArticleService{
Article getArticle(String shopId, Integer articleId);
}
ArticleServiceImpl.java:
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import com.google.gson.Gson;
#Path("/")
#Consumes(MediaType.APPLICATION_JSON + ";charset=UTF-8")
#Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
public class ArticleServiceImpl implements ArticleService {
public ArticleServiceImpl() {
super();
}
#GET
#Path("/{shopId}/{articleId}")
public Article getArtikel(
#PathParam("shopId") String shopId,
#PathParam("articleId") Integer articleId) {
System.out.println(String.format("Shop ID: \"%s\"", shopId));
System.out.println(String.format("Article ID: \"%s\"", articleId));
return gson.toJson(new Article(articleId));
}
}
Article.java:
import java.io.Serializable;
import java.math.BigDecimal;
import javax.xml.bind.annotation.XmlRootElement;
#SuppressWarnings("serial")
#XmlRootElement(name = "article")
public class Article implements Serializable {
private String shopId;
private int articleId;
private String name = "Super pet food";
private BigDecimal price = new BigDecimal("1.00");
private int unitsInStock = 1000;
public Article(String shopId, int articleId) {
super();
this.shopId = shopId;
this.articleId = articleId;
}
}
Yes you can do
like below
rest/orders/1/completed
Here rest in rest servlet path , orders for class, then using #Path("{orderId}/completed")
#Path("orders")
public class OrderService {
#GET
#Path("{orderId}/completed")
public String getOrders(#PathParam("orderId") String orderId) {
return "orderId: " + orderId;
}
#GET
#Path("summary")
public String getOrdersSummary() {
return "orders summary";
}
}
Live demo at http://jerseyexample-ravikant.rhcloud.com/rest/orders/1/completed

I can not repository.save(mydata) I am looking for a pretty alternative to JOIN with Spring + Cassandra

I am beginning to touch Cassandra, but I am in trouble because I can not do JOIN.
Since JOIN can not be done with CQL as it is, I thought about looking for alternative means and joining it on the Java application side.
Specifically, I used #OneToMany and I tried joining Entities, but the following error appears.
Is there any good solution?
■Project structure
SpringBoot + Spring Data for Apache Cassandra
Version:
Spring Boot :: (v1.3.5.RELEASE)
spring-data-cassandra-1.3.5.RELEASE
cassandra 2.1.16
■Error log
com.datastax.driver.core.exceptions.InvalidQueryException: Unknown identifier emp
at com.datastax.driver.core.Responses$Error.asException(Responses.java:102) ~[cassandra-driver-core-2.1.9.jar:na]
at com.datastax.driver.core.DefaultResultSetFuture.onSet(DefaultResultSetFuture.java:149) ~[cassandra-driver-core-2.1.9.jar:na]
at com.datastax.driver.core.RequestHandler.setFinalResult(RequestHandler.java:183) ~[cassandra-driver-core-2.1.9.jar:na]
at com.datastax.driver.core.RequestHandler.access$2300(RequestHandler.java:44) ~[cassandra-driver-core-2.1.9.jar:na]
at com.datastax.driver.core.RequestHandler$SpeculativeExecution.setFinalResult(RequestHandler.java:751) ~[cassandra-driver-core-2.1.9.jar:na]
■ Source: Controller
#RequestMapping(value = "/DepartmentsCassandra/form", method = RequestMethod.POST)
#Transactional(readOnly=false)
public ModelAndView form(
#RequestParam("department_id") int department_id,
#RequestParam("department_name") String department_name,
ModelAndView mav){
Departments mydata = new Departments();
mydata.setDepartment_id(department_id);
mydata.setDepartment_name(department_name);
repository.save(mydata);// ← Error occurred !!!
return new ModelAndView("redirect:/DepartmentsCassandra");
}
■ Source: Entity: Departments
package com.example.cassandra.entity;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import org.springframework.cassandra.core.PrimaryKeyType;
import org.springframework.data.cassandra.mapping.Column;
import org.springframework.data.cassandra.mapping.PrimaryKeyColumn;
import org.springframework.data.cassandra.mapping.Table;
#Table(value="departments")
public class Departments {
#PrimaryKeyColumn(name = "department_id",ordinal = 1,type = PrimaryKeyType.PARTITIONED)
private int department_id;
#Column(value = "department_name")
private String department_name;
public Departments(int department_id,String department_name){
this.department_id = department_id;
this.department_name = department_name;
}
#OneToMany(fetch=FetchType.EAGER)
#JoinColumn(name="department_id",insertable=false,updatable=false)
private List<Employees> emp = new ArrayList<Employees>();
Sooo, "client side join" is a general anti-pattern using Cassandra, since you do two queries instead of one every time and hence lose the performance gain.
The way to go is creating a broad table for each query - including all "joined" data.
So in your case, create a table employee_by_department or something alike.
Check out the introductory courses on datastax.com - they are great :-)

Spring Boot REST Controller issues

I am having a very strange issue with a Rest Controller. I have a very basic rest controller.
package com.therealdanvega.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.therealdanvega.domain.Post;
import com.therealdanvega.service.PostService;
#RestController
public class PostController {
private PostService postService;
#Autowired
public PostController(PostService postService){
this.postService = postService;
}
#RequestMapping("posts/test")
public String test(){
return "test...";
}
#RequestMapping( name="/posts/", method=RequestMethod.GET )
public Iterable<Post> list(){
return postService.list();
}
}
That calls a service
package com.therealdanvega.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.therealdanvega.domain.Post;
import com.therealdanvega.repository.PostRepository;
#Service
public class PostService {
private PostRepository postRepository;
#Autowired
public PostService(PostRepository postRepository){
this.postRepository = postRepository;
}
public Iterable<Post> list(){
return postRepository.findAll();
}
}
That calls a repository to fetch the data.
package com.therealdanvega.repository;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import com.therealdanvega.domain.Post;
#Repository
public interface PostRepository extends CrudRepository<Post, Long> {
Post findFirstByOrderByPostedOnDesc();
List<Post> findAllByOrderByPostedOnDesc();
Post findBySlug(String slug);
}
I am using an H2 in memory database and I only have a single Post record in there and can confirm so by going to the H2 console and running a select again the Post table.
If I visit the /test URL I get exactly what I am expecting which is the string "test..." printed to the browser. If I try and list all of the posts (which again is only 1) the browser starts looping over and over and continue to print out a JSON representing of the 1 post so many times that the application crashes and I see this in the console
2015-11-07 17:58:42.959 ERROR 5546 --- [nio-8080-exec-1]
o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for
servlet dispatcherServlet threw exception
java.lang.IllegalStateException: getOutputStream() has already been
called for this response
This is what my browser looks like when I visit /posts which should only list 1
Post Domain Class
package com.therealdanvega.domain;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.springframework.data.annotation.CreatedDate;
#Entity
public class Post {
#Id #GeneratedValue
private Long id;
private String title;
#Column(columnDefinition = "TEXT")
private String body;
#Column(columnDefinition = "TEXT")
private String teaser;
private String slug;
#CreatedDate
#Temporal(TemporalType.TIMESTAMP)
private Date postedOn;
#ManyToOne
private Author author;
#SuppressWarnings("unused")
private Post(){
}
public Post(String title){
this.setTitle(title);
}
// getters & setters
}
Does anyone know what I am doing wrong or missing here? Why isn't it just display the 1 record in JSON format?
It seems that your Post object has a circular reference. The Author object in your Post object has a list of Posts objects and so on. Try putting the #JsonIgnore annotation on the author attribute of your post object.
You can also use the #JsonBackReference and #JsonManagedReference to solve the problem.
From the Jackson documentation :
Object references, identity
#JsonManagedReference, #JsonBackReference: pair of annotations used to
indicate and handle parent/child relationships expressed with pair of
matching properties #JsonIdentityInfo: class/property annotation used
to indicate that Object Identity is to be used when
serializing/deserializing values, such that multiple references to a
single Java Object can be properly deserialized. This can be used to
properly deal with cyclic object graphs and directed-acyclic graphs.
I believe your Posts domain object contains Author domain object, that in turn in it's posts field contains all the posts by that author, which in turn contains author that contains posts... you see where I'm going with this.
It's probably best that you use fetch or load graphs to optimize your query's fetch strategy.

Categories

Resources