I have ran into a complete stop trying to develop a service using R2dbc. I am building an integration test for it. The service has a huge problem, which while preparing the test data in the integration test I am able to to build, save it in db (h2) and receive the test data from h2 db for entity user.
Sadly when I call the endpoint I am trying to test where data for user has been saved while setting up the test data for user, there in a service class r2dbc fails to instantiate User class while trying to get the object from db. I have defined an empty constructor for User. I am completely stuck at understanding why this instantiation fails. When setting up the test data in the integration test I am able to save and receive rows, but when the integration test reaches the endpoint and the service it just returns the following error while querying:
org.springframework.data.mapping.model.MappingInstantiationException: Failed to instantiate com.x.base.domain.User using constructor com.x.base.domain.User() with arguments
at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator$EntityInstantiatorAdapter.createInstance(ClassGeneratingEntityInstantiator.java:240)
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Assembly trace from producer [reactor.core.publisher.FluxMapFuseable] :
reactor.core.publisher.Flux.map(Flux.java:6091)
io.r2dbc.h2.H2Result.map(H2Result.java:67)
Error has been observed at the following site(s):
The entity:
#Table("user")
public class User extends AbstractAuditingEntity implements Serializable {
User() {
}
#Id
#Column("id")
#Size(max = 100)
#NotNull
private String id;
#NotNull
#Pattern(regexp = Constants.LOGIN_REGEX)
#Size(min = 1, max = 50)
#Column("login")
private String login;
#Size(max = 50)
#Column("first_name")
private String firstName;
#Size(max = 50)
#Column("last_name")
private String lastName;
....
(getters and builder down below)
}
I have debugged the instantiation to a really low level and have not yet seen anything to cause this error. The DB is found and also the reflections (I do not use setter classes).
The integration test:
// here user is defined and populated - I only use block in the integration tests.
var user = userRepository.create(user).block();
// here I test if the method in the service should work
var userByLogin = userRepository.findOneByLogin(user.getLogin())
TreeDTO treeDTO = webTestClient
.get()
.uri("/api/x/x/" + user.getId())
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus()
.isOk()
.expectHeader()
.contentType(MediaType.APPLICATION_JSON)
.returnResult(TreeDTO.class)
.getResponseBody()
.blockFirst();
Service method, where the instantiation fails:
#Transactional(readOnly = true)
public Mono<DevelopmentTreeDTO> getTreeByUserId(String userId) {
return SecurityUtils.getCurrentUserLogin()
.flatMap(login -> userRepository.findOneByLogin(login) -> error is thrown here (user is authenticated and login is present in securityUtils)
.map(user -> { ...
I tried to keep it as short as possible, but I just am scratching my head with this. Any recommendation is extremely welcome. Thank you in advance!
Related
I've created spring application for CRUD. I can easily write into server data like string,Long,blob but When I try to retrieve it from server. I've encountered with difficulty which byte array from server gives in BigInteger from server. How I could get data in byte array instead of BigInteger?When I write in insert byte array this data which column is BLOB. Here is my code
Repository
public interface ArriveRepository extends JpaRepository<ArriveEntity,Long>
{
#Query(value = "select arrive.time,air_lines.image,arrive.flight,arrive.destination_uzb," +
"arrive.destination_eng,arrive.destination_rus,arrive.status,arrive.status_time " +
"from arrive inner join air_lines on air_lines.id = arrive.airline_id where arrive.arrive_date = (:date1)",nativeQuery = true)
List<Object[]> getForArriveTerminal(#Param("date1") LocalDate date1);
}
When I retrieve data from server I'm using this class
ArriveTerminalDto
public class ArriveTerminalDto {
private String time;
private BigInteger logo;
private String flight;
private String destinationUzb;
private String destinationEng;
private String destinationRus;
private String status;
private String statusTime;
//getter setter}
Service class
public List<ArriveTerminalDto> getToShow(LocalDate date1)
{
List<ArriveTerminalDto> list = new ArrayList<>();
List<Object[]> list1 = arriveRepository.getForArriveTerminal(date1);
for(Object[] objects: list1)
{
ArriveTerminalDto arriveTerminalDto = new ArriveTerminalDto();
arriveTerminalDto.setTime((String)objects[0]);
arriveTerminalDto.setLogo((BigInteger) objects[1]);
arriveTerminalDto.setFlight((String) objects[2]);
arriveTerminalDto.setDestinationUzb((String) objects[3]);
arriveTerminalDto.setDestinationRus((String) objects[4]);
arriveTerminalDto.setDestinationEng((String) objects[5]);
arriveTerminalDto.setStatus((String) objects[6]);
list.add(arriveTerminalDto);
}
return list;
}
This code works but it didn't give me byte array from server.
When I try to change BigInteger into byt[] array it gives me following errors
from postman
{
"timestamp": "2019-01-28T09:33:52.038+0000",
"status": 500,
"error": "Internal Server Error",
"message": "java.math.BigInteger cannot be cast to [B",
"path": "/arrive/terminal/date=2019-01-27"
}
Changed Object into ArriveTerminalDto but still it give error my following repo
public interface ArriveRepository extends JpaRepository<ArriveEntity,Long>
{
#Query(value = "select arrive.time,air_lines.image,arrive.flight,arrive.destination_uzb," +
"arrive.destination_eng,arrive.destination_rus,arrive.status,arrive.status_time " +
"from arrive inner join air_lines on air_lines.id = arrive.airline_id where arrive.arrive_date = (:date1)",nativeQuery = true)
List<ArriveTerminalDto> getForArriveTerminal(#Param("date1") LocalDate date1);
}
Why don't you take a look at the Spring Content community project. This project allows you to associate content with Spring Data entities. Think Spring Data but for Content, or unstructured data. This can also give you REST endpoints for the content as well, like Spring Data REST.
This approach will give you a clear abstraction for your content with implementations for many different types of storage. It is stream-based, rather than byte-based. Using byte[] won't work if you want to transfer very large files. Also getting databases to stream properly is very idiosyncratic. You probably don't want to figure all that out yourself when Spring Content already has.
This is pretty easy to add to your existing projects. I am not sure if you are using Spring Boot, or not. I'll give a non-spring boot example:
pom.xml
<!-- Java API -->
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-jpa</artifactId>
<version>0.5.0</version>
</dependency>
<!-- REST API (if you want it) -->
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-rest</artifactId>
<version>0.5.0</version>
</dependency>
Configuration
#Configuration
#EnableJpaStores
#Import("org.springframework.content.rest.config.RestConfiguration.class")
public class ContentConfig {
// schema management
//
#Value("/org/springframework/content/jpa/schema-drop-mysql.sql")
private Resource dropContentTables;
#Value("/org/springframework/content/jpa/schema-mysql.sql")
private Resource createContentTables;
#Bean
DataSourceInitializer datasourceInitializer() {
ResourceDatabasePopulator databasePopulator =
new ResourceDatabasePopulator();
databasePopulator.addScript(dropContentTables);
databasePopulator.addScript(createContentTables);
databasePopulator.setIgnoreFailedDrops(true);
DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource());
initializer.setDatabasePopulator(databasePopulator);
return initializer;
}
}
To associate content, add Spring Content annotations to your account entity.
ArriveEntity.java
#Entity
public class ArriveEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
.. existing fields...
#ContentId
private String contentId;
#ContentLength
private long contentLength = 0L;
// if you have rest endpoints
#MimeType
private String mimeType = "text/plain";
}
Create a "store":
ArrivEntityContentStore.java
#StoreRestResource(path="arriveEntityContent)
public interface ArrivEntityContentStore extends ContentStore<ArriveEntity, String> {
}
This is all you need to create REST endpoints # /arriveEntityContent. When your application starts, Spring Content will look at your dependencies (seeing Spring Content JPA/REST), look at your ArrivEntityContentStore interface and inject an implementation of that interface for JPA. It will also inject a #Controller that forwards http requests to that implementation. This saves you having to implement any of this yourself which I think is what you are after.
So...
To access content with a Java API, auto-wire ArrivEntityContentStore and use it methods.
Or to access content with a REST API:
curl -X POST /arriveEntityContent/{arriveEntityId}
with a multipart/form-data request will store the image in the database and associate it with the account entity whose id is itemId.
curl /arriveEntityContent/{arriveEntityId}
will fetch it again and so on...supports full CRUD.
There are a couple of getting started guides here. The reference guide is here. And there is a tutorial video here. The coding bit starts about 1/2 way through.
HTH
Try to change entity definition to handle byte[] directly, but suggest JPA to interpret it as Lob. You can do it with #Lob annotation:
public class ArriveTerminalDto {
private String time;
#Lob
private byte[] logo;
private String flight;
private String destinationUzb;
private String destinationEng;
private String destinationRus;
private String status;
private String statusTime;
}
Laster, as #Clijsters suggested, you can change your repo to return List<ArriveTerminalDto>.
I am using springBoot 2 and I am trying to validate the objects in a List via:
#RequestMapping(value = "/bets",
produces = {"application/json"},
consumes = {"application/json"},
method = RequestMethod.POST
)
void postBets(#RequestBody List<#Valid Bet> bets);
and Bet class has #NotNull annotations on certain attributes.
import javax.validation.constraints.NotNull;
public class Bet extends BetMessage {
#NotNull
private String categoryName;
#NotNull
private String marketName = null;
#NotNull
private OffsetDateTime startTime = null;
#NotNull
private String betName = null;
I have also added the spring-boot-starter-validation artifact to my build file but still no validation is happening.
As a workaround I have implemented the popular answer in the question below (ValidList class) and validation is working as expected; however I think that I am missing something obvious and the solution is now part of the validation library.
Validation of a list of objects in Spring
You may want to write a wrapper which contains your list of Bet because then your wrapper will conform to JavaBean specs and the validations can be applied.
Below Answer might help in this case.
#Valid on list of beans in REST service
I am experiencing some strange behaviour with Hibernate Validator 6.0.9.Final when validating an object. Given the following POJO:
public class Log {
private Long uid;
#javax.validation.constraints.NotEmpty
#javax.validation.constraints.NotNull
private String value;
private long reference;
private String type;
private String createdDt;
private String createdBy;
private Boolean actioned;
private Long dxr;
// Constructors..
// Getters and setters...
// Equals and hashcode overrides...
}
Now, if I validate the entity using a new instance JUnit test I get the following output:
#Test
public void test() {
Log cLog = new Log();
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator beanvalidator = validatorFactory.getValidator();
Set<ConstraintViolation<CommunicationLog>> constraintViolations = beanvalidator.validate(cLog);
System.out.println(constraintViolations);
}
[ConstraintViolationImpl{interpolatedMessage='must not be null', propertyPath=value, rootBeanClass=class Log, messageTemplate='{javax.validation.constraints.NotNull.message}'},
ConstraintViolationImpl{interpolatedMessage='must not be empty', propertyPath=value, rootBeanClass=class Log, messageTemplate='{javax.validation.constraints.NotEmpty.message}'}]
Which is fine. However, if I run this same code on a Hibernate/Jersey project, only the #NotNull validation runs. I cannot seem to get the #NotEmpty validator to run on the object.
I have tried removing the #NotNull validator (as the #NotEmpty validator takes care of it) but I left it on for completeness to demonstrate that the #NotEmpty validator is not returning anything in the image above.
I do not understand why it is not running when deployed to a web project but works fine when running under a JUnit test. Is there something I am missing here?
Okay, so I have resolved this. I hadn't realised that I had deployed the project to the wrong server. I had deployed it to a Payara 4.1.1.171 server instead of a Payara 5.181 server. However, I would have expected some kind of information to have been displayed - perhaps when deploying.
We're using MongoDB and Spring Data in our Spring Boot app. Another developer has earlier written a service, using MongoTemplate's findAndModify method, and some additional code that uses that service. It worked fine, even in production.
I've branched and went on adding some new features (completely new code) and needed to call that service. Service doesn't work for me, even though I didn't even touch the service code. Moreover, even the code the other guy wrote before (it's a REST controller calling the service) doesn't work now. On the master branch, everything works like it should.
Here's the service:
#Autowired
private MongoTemplate mongo;
public void addRetention(Date when, String userId, Platform platform) {
LocalDateTime dateTime = LocalDateTime.ofEpochSecond(when.getTime()/1000, 0, ZoneOffset.UTC);
int yyyymm = 100 * dateTime.getYear() + dateTime.getMonthValue();
Retention r = mongo.findAndModify( // the exception is coming from this line
new Query(Criteria.where("userId").is(userId).and("yyyymm").is(yyyymm)),
new Update().inc("count", 1).set("platform", platform),
new FindAndModifyOptions().upsert(true).returnNew(true), Retention.class
);
addRealTimeRetention(userId, r, yyyymm);
}
How I call the service:
retentionService.addRetention(timeService.timeToUtil(usage2.getDate()),
usage2.getUserId(), platform);
And the stack trace on PasteBin. The exception message is:
IllegalArgumentException: Target bean is not of type of the persistent entity!
EDIT: Here's the Retention.java class:
#Document
#JsonInclude(JsonInclude.Include.NON_NULL)
#CompoundIndexes({
#CompoundIndex(name = "userid_yyyymm_idx", def = "{ userId: 1, yyyymm: 1 }")
})
public class Retention {
#Id
private String id;
#Indexed
private String userId;
#Indexed
private int yyyymm;
private int count;
private Platform platform;
// setters and getters...
}
FINAL EDIT: I solved this by deleting spring devtools dependency in pom.xml. I'm not sure how that dependency has anything to do with this exception. Found the solution here. Thank you to everyone who helped out.
I'm using Spring Boot 1.5.4, Spring Data REST, HATEOAS. I'm exposing REST endpoints to be consumed from a Angular client.
I'm using spring.data.rest.enable-enum-translation=true to convert enums. It works fine both in GET and POST requests exposed from Spring Data REST from repositories.
I added a custom method in a repository:
#Transactional(readOnly = true)
#PreAuthorize("isAuthenticated()")
public interface TransitCertificateRepository extends PagingAndSortingRepository<TransitCertificate, Long> {
#Query("SELECT t FROM TransitCertificate t WHERE :states IS NULL OR status IN (:states) ")
public Page<TransitCertificate> findAllByParameters(
#Param("states") #RequestParam(value = "states", required = false) List<TransitCertificateStatus> states, Pageable pageable);
This is the enum:
public enum TransitCertificateStatus {
PENDING, USED, CANCELED, ARCHIVED
}
This is the relevant part of the model:
#Entity
#EntityListeners(TransitCertificateListener.class)
public class TransitCertificate extends AbstractEntity {
private static final long serialVersionUID = 5978999252424024545L;
#NotNull(message = "The status cannot be empty")
#Column(nullable = false)
#Enumerated(EnumType.STRING)
private TransitCertificateStatus status = TransitCertificateStatus.PENDING;
In rest-messages.properties I've translation for the enum like:
server.model.enums.TransitCertificateStatus.PENDING = Pending
server.model.enums.TransitCertificateStatus.USED = Used
When the client try to call my method findAllByParameters and sends a array of String (translated how the server sent back), the conversion on the server fails.
I don't understand why the conversion works in save() method, for example, but not in my method.
Furthemore if the client sends me 2 states, Spring returns this error:
Parameter value element [USED] did not match expected type [server.model.enums.TransitCertificateStatus (n/a)]; nested exception is java.lang.IllegalArgumentException: Parameter value element [USED] did not match expected type [server.model.enums.TransitCertificateStatus (n/a)]
So I guess I've two problems:
For some reason Spring is not able to convert a String[] to a List<TransitCertificateStatus> even if the value is exactly the one defined in the TransitCertificateStatus (PENDING, USED, CANCELED, ARCHIVED)
Spring is not able to convert the String the client send, to the right enum when the value is one of that defined in rest-messages.properties (Pending, Used, etc).
Is there a way to solve the problem is a elegant way using internal facilities of Spring Data REST (I point out enum transation works in save() method) without reinventing the wheel?
I ended up to solve the problem in this way:
I created a custom #RepositoryRestController
I created my method
I get the enum from the client like a String and then I convert it. In this way the client can send also the translated string for the enum
This a piece of code:
#PostMapping(path = "/licensePlates/searches")
public ResponseEntity<?> search(#RequestBody(required = true) List<Filter> filters, Pageable pageable, Locale locale,
PersistentEntityResourceAssembler resourceAssembler) {
EngineType engineType = enumTranslator.fromText(EngineType.class, filterMap.get("engineType"));
You have to inject enumTranslation in this way:
#Autowired
private EnumTranslator enumTranslator;
Not sure is the best way but that solved my problem with little code.