Mockito how to test save method with DTO - spring-boot

I dont know how can i test save method with mockito. The problem is that the test is incorrect because I am creating a new object in the service and I have no idea how to fix it
Service
#Validated
public class ProductService {
private final CategoryService categoryService;
public ProductService(CategoryService categoryService){
this.categoryService = categoryService;
}
public void addProduct(Long categoryId,#Valid AddProductDto addProductDto) throws DataAccessException{
CategoryModel categoryModel = categoryService.getCategoryById(categoryId);
ProductModel productModel = new ProductModel();
productModel.setProducent(addProductDto.getProducent());
productModel.setPrice(addProductDto.getPrice());
productModel.setName(addProductDto.getName());
productModel.setSlider(addProductDto.getSlider());
productModel.setImage(addProductDto.getImage());
productModel.setDescription(addProductDto.getDescription());
productModel.setQuantityAvailable(addProductDto.getQuantityAvailable());
productModel.setCategoryModel(categoryModel);
productRepository.save(productModel);
}
}
DTO:
#Data
#Builder
public class AddProductDto implements Serializable {
#NotEmpty(message = "Wprowadź poprawną nazwę nazwe")
#Length(min = 3, max = 220, message = "Wprowadź poprawną długość nazwy przedmiotu")
private final String name;
#NotNull(message = "Uzupełnij Slider")
private final Boolean slider;
#NotNull(message = "Wprowadź poprawną ilość")
#Min(message = "Wprowadź poprawną ilość", value = 0)
private Integer quantityAvailable;
#Length(min = 1, max = 220, message = "Wprowadź poprawną długość nazwy producenta")
#NotEmpty(message = "Wprowadź poprawnego producenta")
private final String producent;
#Length(min = 5, max = 220, message = "Wprowadź poprawny opis przedmiotu")
#NotEmpty(message = "Wprowadź poprawny opis")
private final String description;
#NotEmpty(message = "Wprowadź poprawne zdjęcie")
#Length(min = 1, max = 240, message = "Wprowadź poprawne zdjęcie przedmiotu")
private final String image;
#Min(message = "Wprowadź poprawną cenę", value = 1)
#NotNull(message = "Wprowadź cenę")
private final Double price;
}
My test:
#Test
void testAddProductSuccess(){
//given
AddProductDto addProductDto = AddProductDto.builder()
.producent("Logitech")
.price(1.0)
.name("Logitech G-403")
.slider(false)
.image("myszka1.jpg")
.description("Dzięki przemyślanej budowie waży jedynie 59 g, dzięki czemu Twoja dłoń nie męczy się podczas użytkowania.")
.quantityAvailable(100)
.build();
ProductModel productModel = ProductModel.builder()
.producent("Logitech")
.price(1.0)
.name("Logitech G-403")
.slider(false)
.image("myszka1.jpg")
.description("Dzięki przemyślanej budowie waży jedynie 59 g, dzięki czemu Twoja dłoń nie męczy się podczas użytkowania.")
.quantityAvailable(100)
.build();
Set<ConstraintViolation<AddProductDto>> violations = validator.validate(addProductDto);
//when
productService.addProduct(1L,addProductDto);
//verify
verify(productRepository,times(1)).save(productModel);
assertTrue(violations.isEmpty());
}
Test Result
Argument(s) are different! Wanted:
productRepository.save(
model.ProductModel#55a88417
);
Actual invocations have different arguments:
productRepository.save(
ProductModel#18acfe88
);
The test is incorrect because in the service I create a new ProductModel object and it is not the same object that I am checking. And I have no idea how to fix it.

Best way to test JpaRepository is using the #DataJpaTest annotation on you test class. Inject repository under test and that's it. You should forget the hell of creating when and then statements. You can find the reference guide here

Related

How to update JPA/Hibernate entities with Apache Camel

I have a spring boot project with apache camel (Using maven dependencies: camel-spring-boot-starter, camel-jpa-starter, camel-endpointdsl).
There are the following 3 entities:
#Entity
#Table(name = RawDataDelivery.TABLE_NAME)
#BatchSize(size = 10)
public class RawDataDelivery extends PersistentObjectWithCreationDate {
protected static final String TABLE_NAME = "raw_data_delivery";
private static final String COLUMN_CONFIGURATION_ID = "configuration_id";
private static final String COLUMN_SCOPED_CALCULATED = "scopes_calculated";
#Column(nullable = false, name = COLUMN_SCOPED_CALCULATED)
private boolean scopesCalculated;
#OneToMany(mappedBy = "rawDataDelivery", fetch = FetchType.LAZY)
private Set<RawDataFile> files = new HashSet<>();
#CollectionTable(name = "processed_scopes_per_delivery")
#ElementCollection(targetClass = String.class)
private Set<String> processedScopes = new HashSet<>();
// Getter/Setter
}
#Entity
#Table(name = RawDataFile.TABLE_NAME)
#BatchSize(size = 100)
public class RawDataFile extends PersistentObjectWithCreationDate {
protected static final String TABLE_NAME = "raw_data_files";
private static final String COLUMN_CONFIGURATION_ID = "configuration_id";
private static final String COLUMN_RAW_DATA_DELIVERY_ID = "raw_data_delivery_id";
private static final String COLUMN_PARENT_ID = "parent_file_id";
private static final String COLUMN_IDENTIFIER = "identifier";
private static final String COLUMN_CONTENT = "content";
private static final String COLUMN_FILE_SIZE_IN_BYTES = "file_size_in_bytes";
#ManyToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = COLUMN_RAW_DATA_DELIVERY_ID)
private RawDataDelivery rawDataDelivery;
#Column(name = COLUMN_IDENTIFIER, nullable = false)
private String identifier;
#Lob
#Column(name = COLUMN_CONTENT, nullable = true)
private Blob content;
#Column(name = COLUMN_FILE_SIZE_IN_BYTES, nullable = false)
private long fileSizeInBytes;
// Getter/Setter
}
#Entity
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
#Table(name = RawDataRecord.TABLE_NAME, uniqueConstraints = ...)
public class RawDataRecord extends PersistentObjectWithCreationDate {
public static final String TABLE_NAME = "raw_data_records";
static final String COLUMN_RAW_DATA_FILE_ID = "raw_data_file_id";
static final String COLUMN_INDEX = "index";
static final String COLUMN_CONTENT = "content";
static final String COLUMN_HASHCODE = "hashcode";
static final String COLUMN_SCOPE = "scope";
#ManyToOne(optional = false)
#JoinColumn(name = COLUMN_RAW_DATA_FILE_ID)
private RawDataFile rawDataFile;
#Column(name = COLUMN_INDEX, nullable = false)
private long index;
#Lob
#Type(type = "jsonb")
#Column(name = COLUMN_CONTENT, nullable = false, columnDefinition = "jsonb")
private String content;
#Column(name = COLUMN_HASHCODE, nullable = false)
private String hashCode;
#Column(name = COLUMN_SCOPE, nullable = true)
private String scope;
}
What I try to do is to build a route with apache camel which selects all deliveries having the flag "scopesCalculated" == false and calculate/update the scope variable of all records attached to the files of this deliveries. This should happen in one database transaction. If all scopes are updated I want to set the scopesCalculated flag to true and commit the changes to the database (in my case postgresql).
What I have so far is this:
String r3RouteId = ...;
var dataSource3 = jpa(RawDataDelivery.class.getName())
.lockModeType(LockModeType.NONE)
.delay(60).timeUnit(TimeUnit.SECONDS)
.consumeDelete(false)
.query("select rdd from RawDataDelivery rdd where rdd.scopesCalculated is false and rdd.configuration.id = " + configuration.getId())
;
from(dataSource3)
.routeId(r3RouteId)
.routeDescription(configuration.getName())
.messageHistory()
.transacted()
.process(exchange -> {
RawDataDelivery rawDataDelivery = exchange.getIn().getBody(RawDataDelivery.class);
rawDataDelivery.setScopesCalculated(true);
})
.transform(new Expression() {
#Override
public <T> T evaluate(Exchange exchange, Class<T> type) {
RawDataDelivery rawDataDelivery = exchange.getIn().getBody(RawDataDelivery.class);
return (T)rawDataDelivery.getFiles();
}
})
.split(bodyAs(Iterator.class)).streaming()
.transform(new Expression() {
#Override
public <T> T evaluate(Exchange exchange, Class<T> type) {
RawDataFile rawDataFile = exchange.getIn().getBody(RawDataFile.class);
// rawDataRecordJpaRepository is an autowired interface by spring with the following method:
// #Lock(value = LockModeType.NONE)
// Stream<RawDataRecord> findByRawDataFile(RawDataFile rawDataFile);
// we may have many records per file (100k and more), so we don't want to keep them all in memory.
// instead we try to stream the resultset and aggregate them by 500 partitions for processing
return (T)rawDataRecordJpaRepository.findByRawDataFile(rawDataFile);
}
})
.split(bodyAs(Iterator.class)).streaming()
.aggregate(constant("all"), new GroupedBodyAggregationStrategy())
.completionSize(500)
.completionTimeout(TimeUnit.SECONDS.toMillis(5))
.process(exchange -> {
List<RawDataRecord> rawDataRecords = exchange.getIn().getBody(List.class);
for (RawDataRecord rawDataRecord : rawDataRecords) {
rawDataRecord.setScope("abc");
}
})
;
Basically this is working, but I have the problem that the records of the last partition will not be updated. In my example I have 43782 records but only 43500 are updated. 282 remain with scope == null.
I really don't understand the JPA transaction and session management of camel and I can't find some examples on how to update JPA/Hibernate entities with camel (without using SQL component).
I already tried some solutions but none of them are working. Most attempts end with "EntityManager/Session closed", "no transaction is in progress" or "Batch update failed. Expected result 1 but was 0", ...
I tried the following:
to set jpa(...).joinTransaction(false).advanced().sharedEntityManager(true)
use .enrich(jpa(RawDataRecord.class.getName()).query("select rec from RawDataRecord rec where rawDataFile = ${body}")) instead of .transform(...) with JPA repository for the records
using hibernate session from camel headers to update/save/flush entities: "Session session = exchange.getIn().getHeader(JpaConstants.ENTITY_MANAGER, Session.class);"
try to update over new jpa component at the end of the route:
.split(bodyAs(Iterator.class)).streaming()
.to(jpa(RawDataRecord.class.getName()).usePersist(false).flushOnSend(false))
Do you have any other ideas / recommendations?

Junit how to achieve100% coverage for Model class

I have a model class:
#Builder
#Data
#Entity
#AllArgsConstructor
#NoArgsConstructor
public class Employee {
#GeneratedValue(strategy = GenerationType.AUTO)
#Type(type="uuid-char")
#Column(updatable = false, nullable = false, unique = true)
#Id
private UUID id;
#Column(updatable = true, nullable = false, unique = true)
#Email(message = "Enter a valid email")
private String email;
#NotNull(message = "First name cannot be empty")
#Size(min = 3, message = "First name character must be more than 3!")
private String firstName;
#Size(min = 3, message = "Last name character must be more than 3!")
private String lastName;
#Range(min = 21, max = 55, message = "Age must be between 21 and 55")
private int age;
#JsonIgnore
private Double accBalance;
#NotNull(message = "Gender cannot be empty")
private String gender;
#NotNull(message = "Country cannot be empty")
private String country;
#JsonProperty("Job Scope")
private String designation;
#CreationTimestamp
private Date createdAt;
#DateTimeFormat
private Date birthDate;
}
And this is my test class:
class EmployeeTest {
#Test
public void testObjectMethod() {
Employee object = new Employee();
object.equals(new Employee());
object.hashCode();
object.toString();
}
#Test
public void testAll() {
Employee object = new Employee();
object.equals(Employee.builder().build());
}
}
And this is my coverage. Basically it only covers 73.8%. What other tests do I need to do to achieve 100%? As this covers quite a lot and doesn't need much of thinking, I would like to target 100%. Appreciate any help or pointers.
coverage
You need to do following
write test for equals
write test for hashcode
write test case for constructor no-arg and all arg
test case for setter and getter for all attribute
you can write assertNotNull for hashCode various tests.

How to do a ManyToMany relationship insert

I am studying spring boot data using this API SWAPI, I did almost things but now I dont know how to map the relationship about two lists, above you can see my code and entities.
Entity Film
#Data
#Entity
public class Film extends Persistent<Long> {
private String title;
#JsonProperty(value = "episode_id")
private int episodeId;
#JsonProperty(value = "opening_crawl")
#Column(columnDefinition = "CLOB")
private String openingCrawl;
private String director;
private String producer;
#JsonDeserialize(converter = StringToLocalDateConverter.class)
#JsonProperty(value = "release_date")
private LocalDate releaseDate;
#JsonDeserialize(converter = ApiURLToEntitiesConverter.class)
#ManyToMany(mappedBy = "films")
private List<Person> characters;
#JsonDeserialize(converter = StringToLocalDateTimeConverter.class)
private LocalDateTime created;
#JsonDeserialize(converter = StringToLocalDateTimeConverter.class)
private LocalDateTime edited;
private String url;
}
Entity Person
#Data
#Entity
public class Person extends Persistent<Long> {
private String name;
private String height;
private String mass;
#JsonProperty(value = "hair_color")
private String hairColor;
#JsonProperty(value = "skin_color")
private String skinColor;
#JsonProperty(value = "eye_color")
private String eyeColor;
#JsonProperty(value = "birth_year")
private String birthYear;
private String gender;
#JsonDeserialize(converter = ApiURLToEntityConverter.class)
#JoinColumn(name = "planet_id", foreignKey = #javax.persistence.ForeignKey(name = "none"))
#OneToOne(optional = true)
private Planet homeworld;
#JsonDeserialize(converter = ApiURLToEntitiesConverter.class)
#ManyToMany
#JoinTable(
name = "film_person",
joinColumns = #JoinColumn(name = "film_fk", referencedColumnName = "id", nullable = true),
inverseJoinColumns = #JoinColumn(name = "person_fk", referencedColumnName = "id", nullable = true))
private List<Film> films;
#JsonDeserialize(converter = StringToLocalDateTimeConverter.class)
private LocalDateTime created;
#JsonDeserialize(converter = StringToLocalDateTimeConverter.class)
private LocalDateTime edited;
private String url;
}
I am trying to use the spring jpa method to saveAll
#Override
public List<T> insertAll(List<T> entities) {
for (Persistent entity : entities) {
Set<ConstraintViolation<Persistent>> violations = validator.validate(entity);
if (violations != null && !violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
}
return repository.saveAll(entities);
}
Converter Method
#Override
public List convert(List<String> s) {
if (s == null || s.isEmpty()) {
return null;
}
List objetos = new LinkedList();
for (String url : s) {
if (url.contains("people")) {
objetos.add(Util.getPerson(url));
}
if (url.contains("planets")) {
objetos.add(Util.getPlanet(url));
}
if (url.contains("starships")) {
objetos.add(Util.getStarship(url));
}
if (url.contains("vehicles")) {
objetos.add(Util.getVehicle(url));
}
if (url.contains("species")) {
objetos.add(Util.getSpecie(url));
}
}
return objetos;
}
}
Util method
public static Person getPerson(String characterApiUrl) {
if (characterApiUrl == null || characterApiUrl.isEmpty()) {
return null;
}
Person person = new Person();
person.setId(StringUtil.getIdEntity(characterApiUrl, "people/"));
return person;
}
The relationship table is being created but no populated

PSQLException: ERROR: null value in column "person" violates not-null constraint when trying class-based projection

I cannot solve my problem, although following a documentation regarding class-based projection.
I always get the following error message:
PSQLException: ERROR: null value in column "person" violates not-null constraint
This is my entity class
#Entity
#Table(name="incomeoutgo", schema = "public")
public class IncomeOutgo extends AbstractPersistable<Long> {
#NotNull
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name ="id")
private Long id;
#NotNull
#Column(name="dayofweek")
private Date dayofweek;
#NotNull
#Size(min= 2, max= 100)
#Column(name="position")
private String position;
#NotNull
#Size(min = 5, max = 50)
#Column(name ="person")
private String person;
#NotNull
#Size(min= 1)
#Column(name="income")
private double income;
#NotNull
#Size(min= 2)
#Column(name="outgo")
private double outgo;
}
So does my Repository look like:
#Repository
public interface ChooseMonthRepository extends JpaRepository<IncomeOutgo, Date> {
#Query(value = "SELECT dayofweek, person, position, income, outgo FROM IncomeOutgo WHERE dayofweek >= :start_dayofmonth AND dayofweek <= :end_dayofmonth", nativeQuery = true)
List<DateChoiceDTO> findAllByDate(#Param("start_dayofmonth") Date start_dayofmonth, #Param("end_dayofmonth") Date end_dayofmonth);
}
This is my class-based projection
#AllArgsConstructor
#NoArgsConstructor
#Data
public class DateChoiceDTO {
Date dayofweek;
String person;
String position;
double income;
double outgo;
}
The Service class
#RequiredArgsConstructor
#Service
#Transactional(readOnly=true)
public class DateChoiceService {
private final ChooseMonthRepository incomeOutgoDateChoice;
public List<?> getAllForDateChoice(Date start_dayofmonth) {
Date lastDayOfMonth=java.util.Calendar.getInstance().getTime();
return incomeOutgoDateChoice.findAllByDate(start_dayofmonth, lastDayOfMonth);
}
}
And last but not least my Controller
#RequiredArgsConstructor
#Controller
#Validated
#RequestMapping(value = "/")
public class DateChoiceController {
private static final String DATE_CHOICE_VIEW = "DateChoice";
private final DateChoiceService dateChoiceService;
#GetMapping("/")
public String homeInit(Model model) {
return DATE_CHOICE_VIEW;
}
#PostMapping(value = "/")
public String addUser(#ModelAttribute("incomeoutgo") #Valid DateChoiceDTO dateChoice, Model model, #NotNull BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
return DATE_CHOICE_VIEW;
}
List<?> incomeOutgoList = dateChoiceService.getAllForDateChoice(dateChoice.getDayofweek());
model.addAttribute(DATE_CHOICE_VIEW, incomeOutgoList);
return DATE_CHOICE_VIEW;
}
}
I do not understand why the person column suddenly comes into play?
Maybe someone can tell me what I am doing wrong here?
You are getting following exception because the framework is not sure what to do with the tuple.
No converter found capable of converting from type AbstractJpaQuery$TupleConverter$TupleBackedMap to type DateChoiceDTO
You have to use fully qualified name of the class when you are using construction result set mapping in the query like below:
select new a.b.cSomeDto(t.property1, t.property2) from table t
Here is how i have fixed it:
#Query(value = "SELECT new com.example.samplejdbctemplatecall.DateChoiceDTO" +
"(table.dayofweek, table.person, table.position, table.income, table.outgo) " +
"FROM IncomeOutgo table WHERE table.dayofweek >= :start_dayofmonth AND table.dayofweek <= :end_dayofmonth")
List<DateChoiceDTO> findAllByDateSecond(#Param("start_dayofmonth") Date start_dayofmonth, #Param("end_dayofmonth") Date end_dayofmonth);
But as mentioned by Simon, if you have insert query firing from somewhere else you need to fix that first, because this is a complete retrieval operation and the error you are facing is related to data insertion.
Entire working sample(+ interface projection):
#RequestMapping("/test")
#RestController
class IncomeOutgoController {
private final ChooseMonthRepository chooseMonthRepository;
private int days = 0;
#Autowired
IncomeOutgoController(ChooseMonthRepository chooseMonthRepository) {
this.chooseMonthRepository = chooseMonthRepository;
}
#GetMapping("/interface-projection")
public Object interfaceProjection() {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MONTH, -1);
Date date = calendar.getTime();
calendar = Calendar.getInstance();
return composeResponse("interface-projection", chooseMonthRepository.findAllByDate(date, calendar.getTime()));
}
#GetMapping("/constructor-projection")
public Object constructorProjection() {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MONTH, -1);
Date date = calendar.getTime();
calendar = Calendar.getInstance();
return composeResponse("constructor-projection", chooseMonthRepository.findAllByDateSecond(date, calendar.getTime()));
}
#GetMapping("/store")
public Object addRandomData() {
String randomUuid = UUID.randomUUID().toString();
chooseMonthRepository.save(new IncomeOutgo(null, Calendar.getInstance().getTime(), "random-position: " + randomUuid,
randomUuid, new Random(5000).nextDouble(), new Random(500).nextDouble()));
return "success";
}
private Map<String, Object> composeResponse(String key, Object value) {
final Map<String, Object> response = new HashMap<>();
response.put(key, value);
return response;
}
}
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "incomeoutgo", schema = "public")
class IncomeOutgo extends AbstractPersistable<Long> {
#NotNull
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#NotNull
#Column(name = "dayofweek")
private Date dayofweek;
#NotNull
#Size(min = 2, max = 100)
#Column(name = "position")
private String position;
#NotNull
#Size(min = 5, max = 50)
#Column(name = "person")
private String person;
#NotNull
#Size(min = 1)
#Column(name = "income")
private double income;
#NotNull
#Size(min = 2)
#Column(name = "outgo")
private double outgo;
}
#Repository
interface ChooseMonthRepository extends JpaRepository<IncomeOutgo, Date> {
#Query(value = "SELECT dayofweek, person, position, income, outgo FROM IncomeOutgo WHERE dayofweek >= :start_dayofmonth AND dayofweek <= :end_dayofmonth", nativeQuery = true)
List<DateChoiceDTOInterface> findAllByDate(#Param("start_dayofmonth") Date start_dayofmonth, #Param("end_dayofmonth") Date end_dayofmonth);
#Query(value = "SELECT new com.example.samplejdbctemplatecall.DateChoiceDTO" +
"(table.dayofweek, table.person, table.position, table.income, table.outgo) " +
"FROM IncomeOutgo table WHERE table.dayofweek >= :start_dayofmonth AND table.dayofweek <= :end_dayofmonth")
List<DateChoiceDTO> findAllByDateSecond(#Param("start_dayofmonth") Date start_dayofmonth, #Param("end_dayofmonth") Date end_dayofmonth);
}
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
class DateChoiceDTO {
Date dayofweek;
String person;
String position;
Double income;
Double outgo;
}
interface DateChoiceDTOInterface {
Date getDayOfWeek();
String getPerson();
String getPosition();
Double getIncome();
Double getOutgo();
}
application.properties
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:postgresql://172.17.0.2:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=mysecretpassword
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.show-sql=true

Enhanced Spring Data Rest delivers empty relations

in my current implementation using Spring-Boot, -HATEOAS, -Rest-Data I'm trying to spare some further rest calls and enhance my rest resource for credits to also deliver relations of a credit (see below account as ManyToOne and creditBookingClassPayments as OneToMany).
The problem now is that I'm not able to get it run. The call always delivers empty relations. I really would appreciate some help on this.
Here are the surroundings:
Credit.java
#Entity
#Getter
#Setter
public class Credit {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Setter(NONE)
#Column(name = "id")
private Long itemId;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="account_id", nullable = false)
private Account account;
#OneToMany(mappedBy = "credit")
private List<CreditBookingClassPayment> creditBookingClassPayments = new ArrayList<>();
#NotNull(message="Please enter a valid short name.")
#Column(length = 10, nullable = false)
private String shortName;
#NotNull(message="Please enter a valid name.")
#Column(nullable = false)
private String name;
...
}
CreditRepositoryCustomImpl.java
uses QueryDsl to enhance the credit resource with its realation
...
#Override
public List<Credit> findDistinctByAccountItemIdNew(Long accountId) {
QCredit credit = QCredit.credit;
QAccount account = QAccount.account;
QCreditBookingClassPayment creditBookingClassPayment = QCreditBookingClassPayment.creditBookingClassPayment;
QBookingClass bookingClass = QBookingClass.bookingClass;
BooleanExpression hasAccountItemId = credit.account.itemId.eq(accountId);
List<Credit> credits = from(credit).where(hasAccountItemId)
.innerJoin(credit.account, account)
.leftJoin(credit.creditBookingClassPayments, creditBookingClassPayment)
.leftJoin(creditBookingClassPayment.bookingClass, bookingClass).groupBy(credit.itemId).fetch();
return credits;
}
...
CreditController.java
looking into responseBody here all (account and credit payments) is available for credits
#RepositoryRestController
public class CreditController {
#Autowired
private CreditRepository creditRepository;
#RequestMapping(value = "/credit/search/findAllByAccountItemIdNew", method= RequestMethod.GET, produces = MediaTypes.HAL_JSON_VALUE)
#ResponseBody
public ResponseEntity<Resources<PersistentEntityResource>> findAllByAccountItemIdNew(#RequestParam Long accountId, PersistentEntityResourceAssembler persistentEntityResourceAssembler) {
List<Credit> credits = creditRepository.findDistinctByAccountItemIdNew(accountId);
Resources<PersistentEntityResource> responseBody = new Resources<PersistentEntityResource>(credits.stream()
.map(persistentEntityResourceAssembler::toResource)
.collect(Collectors.toList()));
return ResponseEntity.ok(responseBody);
}
}
CreditResourceIntegrTest.java
here creditResourcesEntity hold the credit but account is null and creditBookingClassPayment is an empty array
#Test
public void testFindAllByAccountItemId() throws URISyntaxException {
URIBuilder builder = new URIBuilder(creditFindAllByAccountItemIdRestUrl);
builder.addParameter("accountId", String.valueOf(EXPECTED_ACCOUNT_ID));
builder.addParameter("projection", "base");
RequestEntity<Void> request = RequestEntity.get(builder.build())
.accept(MediaTypes.HAL_JSON).acceptCharset(Charset.forName("UTF-8")).build();
ResponseEntity<Resources<Resource<Credit>>> creditResourcesEntity =
restTemplate.exchange(request, new ParameterizedTypeReference<Resources<Resource<Credit>>>() {});
assertEquals(HttpStatus.OK, creditResourcesEntity.getStatusCode());
//assertEquals(EXPECTED_CREDIT_COUNT, creditResourcesEntity.getBody().getContent().size());
}
Do I miss something?
Thanks for your help!
Karsten
Okay, PersistentEntityResourceAssembler doesn't support relations. But this could be handled by using projections.
CreditProjection.java
#Projection(name = "base" , types = Credit.class)
public interface CreditProjection {
String getShortName();
String getName();
List<CreditBookingClassPaymentProjection> getCreditBookingClassPayments();
BigDecimal getValue();
BigDecimal getInterestRate();
BigDecimal getMonthlyRate();
}
CreditBookingClassPaymentProjection.java
#Projection(name = "base" , types = CreditBookingClassPayment.class)
public interface CreditBookingClassPaymentProjection {
BookingClass getBookingClass();
CreditPaymentType getCreditPaymentType();
}
CreditController.java
#RepositoryRestController
public class CreditController {
#Autowired
private ProjectionFactory projectionFactory;
#Autowired
private CreditRepository creditRepository;
#RequestMapping(value = "/credit/search/findAllByAccountItemIdNew", method = RequestMethod.GET, produces = MediaTypes.HAL_JSON_VALUE)
#ResponseBody
public ResponseEntity<Resources<?>> findAllByAccountItemIdNew(#RequestParam Long accountId,
PersistentEntityResourceAssembler persistentEntityResourceAssembler) {
List<Credit> credits = creditRepository.findDistinctByAccountItemIdNew(accountId);
List<PersistentEntityResource> creditResources = new ArrayList<>();
for (Credit credit : credits) {
// credit.getCreditBookingClassPayments()
PersistentEntityResource creditResource = persistentEntityResourceAssembler.toResource(credit);
creditResources.add(creditResource);
}
Resources<CreditProjection> responseBody = new Resources<CreditProjection>(credits.stream()
.map(credit -> projectionFactory.createProjection(CreditProjection.class, credit))
.collect(Collectors.toList()));
return ResponseEntity.ok(responseBody);
}
}

Resources