Class based projections using a DTO in Spring Data Jpa is not working - spring-boot

In my Spring Boot application, I am trying to implement a class based projection using DTOs as described at:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections.dtos
I have a domain class that looks like:
#Entity
#Table(name="metadatavalue")
public class MetadataValue {
#Id
#Column(name="metadata_value_id")
private Integer metadataValueId;
#Column(name="resource_id", insertable=false, updatable=false)
private Integer resourceId;
#Column(name="metadata_field_id", insertable=false, updatable=false)
private Integer metadataFieldId;
#Column(name="text_value")
private String textValue;
more class members, getters and setters, etc follow.
I also have a simple DTO class with one member:
public class MetadataDTO {
private String textValue;
public MetadataDTO(String textValue) {
this.textValue = textValue;
}
public String getTextValue() {
return textValue;
}
}
My repository class is:
public interface MetadataValueRepository extends CrudRepository<MetadataValue, Integer> {
#Query("SELECT m from MetadataValue m WHERE m.handle.handle = :handle AND m.resourceTypeId=m.handle.resourceTypeId")
List<MetadataValue> findAllByHandle(#Param("handle") String handle);
#Query("SELECT new path.to.my.package.domain.MetadataDTO(m.textValue AS textValue) FROM MetadataValue m "
+ "WHERE m.handle.handle = :handle AND m.resourceTypeId=m.handle.resourceTypeId")
List<MetadataDTO> findAllByHandleDTO(#Param("handle") String handle);
}
The first method, findAllByHandle works as expected, but when running the second method, findAllByHandleDTO, which I had hoped to return the projection, my application throws the error:
java.lang.IllegalArgumentException: Couldn't find PersistentEntity for type class path.to.my.package.domain.MetadataDTO!
I have also tried extending from JpaRepository with the same result. In another attempt, I tried using an Interface based projection which resulted in an almost identical stacktrace, with an internal class substituted for my class.
My project is spring-boot 2.3.0 with Spring Web, Spring Data JPA, Rest Repositories, and PostgreSQL Driver.
Can anybody help me understand what I am doing wrong and set me in the right direction?
Thanks so much!
Update on this question: (I have added the tag spring-data-rest). Experimenting, I have learned that I can successfully call my method, for instance, in the run() method of my application class. It's only when I call the method as a rest endpoint that I see the error. Is this simply an issue of incompatibility with Spring Data Rest?

Related

Why I receive 404 error use Spring MVC framework?

When I send request http://localhost:8080/pets My server response 404!
The code on github: https://github.com/Teemitze/petstore
I build war file. Version spring 2.2.6.RELEASE
#Controller
#RequestMapping("/pets")
public class PetsController {
#Autowired
PetRepository petRepository;
#PostMapping("/addPet")
public void addPet(Pet pet) {
petRepository.save(pet);
}
#GetMapping
#ModelAttribute
public String pets(Model model) {
List<Pet> petList = new ArrayList<>();
petList.add(getPet());
petList.add(getPet());
petList.add(getPet());
model.addAttribute("pets", petList);
return "allPets";
}
public Pet getPet() {
Pet pet = new Pet();
pet.setId(1L);
pet.setName("Мурзик");
pet.setPrice(100);
pet.setBirthday(Date.valueOf("2019-12-12"));
pet.setSex("М");
return pet;
}
}
I checked out your code and found a few issues.
1) Package structure
Move controller, dto, repo packages to the main package (com.petstore)
Since the main application is inside the (com.petstore) package and the controller is outside the package, so it fails to scan the class.
2) Use annotation #Entity for the Pet entity class with #Id for the id property
3) Remove #ModelAttribute from pets() method since you are not binding any method parameter.
After this, I see the /pets
SpringBoot project requires define some configuration conventions that need to be follow in order to start a minimum application.
Some points you have to consider when you want to start a spring boot application.
For example:
Your SpringBootApplication(PetstoreApplication) class should be in the directory level above your other packages so that it can scan all classes.
If you want to use SpringData JPA you have to manage your model class
#Data
#Entity
public class Pet {
#Id
private long id;
private String name;
private String sex;
private Date birthday;
private byte[] photo;
private int price;
}
because it is handled by respository
public interface PetRepository extends CrudRepository<Pet, Long>
Need minimum configuration for Thymeleaf https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html
You are making a GET request for a resource "/pets" so no need #ModelAttribute in get mapping method
#GetMapping()
public String allPets(Model model) {
Make sure your html files is under resources/templates directory.
Check out the reference docs
spring mvc
spring data jpa

Spring Data Jpa custom repository no property found

I'm trying to create a repository that has a method which doesn't fit the usual JpaRepository with #Query annotations.
I've created a custom repository interface:
public interface CustomVoteRepository {
List<VoteCountResult> countVotesForSession();
}
And the implementation:
#Repository
public class CustomVoteRepositoryImp implements CustomVoteRepository {
private JdbcTemplate jdbcTemplate;
public CustomVoteRepositoryImp(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
#Override
public List<VoteCountResult> countVotesForSession() {
return jdbcTemplate.query("SELECT video_id, COUNT(votes.id) FROM votes WHERE session_id=2 GROUP BY video_id",
new CustomRowMapper());
}
}
However, this gives me this error:
No property countVotesForSession found for type Vote!
I don't understand why it's trying to map a property on the Vote class. I understand it does this for the "auto-generated" method names, but this is supposed to be a custom one.
I've come across this article: https://www.mkyong.com/spring-data/spring-data-add-custom-method-to-repository/ which explains what I'm doing, and yet it's trying to map a property of the model for a custom repository.
I'm sure I missed something stupid.
Thanks!
Edit:
Here's the VoteCountResult dto:
#Data
#AllArgsConstructor
public class VoteCountResult {
private String count;
private String title;
private String url;
}
What if you change your custom method name to votesForSessionCount ? I think this way you won't face with method name conflict.

Service cannot auto-wire in Entity class

I needed a RoleMappingService class(which is annotated by #Service) object into a Employee class (which is annotated by #Entity)
below are my classes
********************* RoleMappingsService class **********************
#Service
public class RoleMappingsService {
#Autowired
RolesMappingDao rolesMappingDao;
public List<RolesMappings> getRolesMappingByauthSystemRole(String authSystemRole) {
return rolesMappingDao.getRolesMappingByauthSystemRole(authSystemRole);
}
}
############### Employee class
#Configurable
#Component
#Entity
#NamedQuery(name = "Employee.findAll", query = "SELECT e FROM Employee e")
public class Employee implements Serializable, UserDetails {
#Autowired
#Transient
RoleMappingsService roleMappingsService;
public static final String STATUS_ACTIVE = "ACTIVE";
public static final String STATUS_INACTIVE = "INACTIVE";
public static final String STATUS_LOCKED = "LOCKED";
public static final String STATUS_ONLEAVE = "ONLEAVE";
public static final String STATUS_EXPIRED = "EXPIRED";
private static final long serialVersionUID = 1L;
#Id
#Column(name = "emp_id")
private String empId;
#Column(name = "emp_password")
private String empPassword;
#Column(name = "emp_email")
private String empEmail;
#Column(name = "emp_address")
private String empAddress;
#Column(name = "emp_age")
private int empAge;
#Column(name = "emp_firstname")
private String empFirstname;
}
Here Autowire is not working for roleMappingsService and the object is always found null. However I tried to autowire same object in some other service and there Autowire is perfectly working.
( I know Entity class is only used for representing database table but in my case I need to set some field values which depend on another table so need to fetch data using service)
JB Nizet is totally right
I'll try to provide more explanations here.
Spring can Autowire only beans, objects that it manages, and not arbitrary objects.
Entities are usually created from within a JPA (Hibernate) and are not something that you want to manage by Spring.
There is a related discussion available here but bottom line you should never do something like this.
Why not?
Here are a couple of questions/reasons:
Maybe these entities will go outside spring context at all (serialization), what should that reference contain? Should we also serialize the service? How?
What will happen if the method that turns to the service will be called "outside" the spring driven application (maybe even in different JVM)?
If there are, say 1000 objects returned by that query, do you really want all of them to reside in application context? Or maybe should they be of "prototype" scope?
As you see, it doesn't play nice with spring concepts. I think the reason for it is that Hibernate and JPA do not "support" an idea of methods inside entities, it's just a different framework. I know there are other frameworks that do allow such a concept, but Hibernate/JPA just doesn't, period
So instead of trying to inject the service into the entity bean, probably you should redesign the application so that the service method will be called from outside, maybe via some facade, and entities will be just populated by query, and then "enriched" with additional information if we're talking about SELECT queries, or, alternatively, some information should be pre-set on entity objects, generated by the Business Logic Layer and only then the entity object should be stored in DB

Spring Data Redis Repository support does not read back embedded complex objects

I have a spring-boot application (1.4RC1, I know it's RC, but Spring Data Redis 1.7.2 is not) where I'm using spring-boot-starter-redis.
The application uses a Spring Data Repository (CrudRepository) which should save an object (using #RedisHash annotation) with String and Boolean properties and one custom class property, which also has only Strings and Longs as properties.
When I save an object (via the repository), everything went fine and I can see all the properties in the database as I would expect.
When I want to read the data from the database (via the repository) I only get the properties from the parent object. The custom class property is null.
I would expect to get the property loaded from the database as well. As the documentation states you can write a custom converter, but since I don't need to do that, when I want to write the data, I shouldn't need to write a reading converter as well.
I wonder if I need to annotate the custom class property, but I couldn't find anything in the documentation. Can you point me in the right direction?
The classes are as follows:
Class sample:
#Data
#EqualsAndHashCode(exclude = {"isActive", "sampleCreated", "sampleConfiguration"})
#RedisHash
public class Sample {
#Id
private String sampleIdentifier;
private Boolean isActive;
private Date sampleCreated;
private SampleConfiguration sampleConfiguration;
public Sample(String sampleIdentifier, SampleConfiguration sampleConfiguration){
this.sampleIdentifier = sampleIdentifier;
this.sampleConfiguration = sampleConfiguration;
}
}
Class SampleConfiguration:
#Data
public class SampleConfiguration {
private String surveyURL;
private Long blockingTime;
private String invitationTitle;
private String invitationText;
private String participateButtonText;
private String doNotParticipateButtonText;
private String optOutButtonText;
private Long frequencyCappingThreshold;
private Long optOutBlockingTime;
}
I added #NoArgsConstructor to my Sample class as Christoph Strobl suggested. Then the repository reads the SampleConfiguration correctly. Thanks, Christoph!

Spring Data Mongo MongoDB DBRef lazy initialization

I'm using Spring + Spring Data MongoDB.
My model is like this:
#Document(collection = "actors")
public class Actor extends DomainEntity {
private String name;
private String surname;
#DBRef(lazy = true)
private List<Class> classes;
The other class is pretty generic, so I don't post it.
My problem is that the list "classes" isn't loaded when i try to access it, the attribute remains being some kind of proxy object.
Example:
Actor a = actorRepository.findOne(id);
//At this moment classes are a proxy object because of the lazy
//Now I try to load the reference and nothing works
a.getClasses();
a.getClasses().size();
a.getClases().get(0).getAttr();
for(Class g:a.getClasses()){
g.getAttr();
}
I considered a ton of options, but no way to make it working...
I'm using spring-data-mongodb-1.7.0.RELEASE and I was able to solve this issue by initializing the lazy-loaded collection in its declaration, for instance:
#DBRef(lazy = true)
private List<Class> classes = new ArrayList<>();

Resources