How to do a Findby In Springboot Mongo repository for Nested Objects - spring-boot

Need your help here in a Spring Data CrudRepository on how to do a findby of nested fields.
My class is of following structure and I need to query using truckId which is of the nested objects
#Document(collection = "unt-truck")
public class TruckModelDTO {
private String type;
private TestDTO testDTO;
}
Class TestDTO.java
public class TestDTO{
private TruckDTO truckDTO;
Private String version;
}
Class TruckDTO.java
public class TruckDTO {
private String truckId;
private String legacySystem;
}
Class TruckRepository.java
#Repository
public interface TruckRepository extends MongoRepository<TruckModelDTO, String> {
// TruckModelDTO findByTruckId(String truckid);
}
So how should I use findby for truckId which is inside a nested class?

You can use with either Entity or DTO as response. but you have to mention the relation between two or three Entity based on you requirement.
I have created Entity. you have to create DTO with same based on entity then apply main DTO as return type in JPA Repository query.
1.Entity
#Entity
public class TruckModel {
private String type;
// mention the relation based on your requirement
private Test test;
}
Entity
#Entity
public class Test {
// here mention the relation based on you requirement
private Truck truck;
Private String version;
}
3.Entity
#Entity
public class Truck {
private String truckId;
private String legacySystem;
}
Repository
#Repository
public interface TruckModelRepository extends MongoRepository<TruckModel, String> {
TruckModelDTO findByTestTruckTruckId(String truckid);
}

Related

Instructing Sping Data MongoDB to use correct mapping between ObjectId and its class

I cannot retrieve the 2nd level nested objects in Spring Data MongoDB
I have nested collection in MongoDB to retrieve with Spring. Imagine this schema
#Data
#Builder
#Document(collection = "emitted")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Emitter{
#Id
private String id;
#Field("installation")
#DocumentReference(lazy = true)
private Installaton installation;
// other fields
#Data
#Builder
#Document(collection = "installation")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Installation {
#Id
private String id;
#Field("subject")
#DocumentReference(lazy = true)
private Subject subject;
// other fields
#Data
#Builder
#Document(collection = "subject")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Subject {
#Id
private String id;
// other fields
Plus, I have MapStruct to map nested object field to string, for the purpose of avoiding cyclic reference introducing the search by id of the collection:
#ObjectFactory
public <T> T map(#NonNull final String id, #TargetType Class<T> type) {
return mongoTemplate.findById(id, type);
}
Everything works at first level, but at nested level I have this error:
Caused by: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [org.bson.types.ObjectId] to type [com.package.collections.Subject]
at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:322)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:195)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:175)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.doConvert(MappingMongoConverter.java:1826)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.doConvert(MappingMongoConverter.java:1818)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.getPotentiallyConvertedSimpleRead(MappingMongoConverter.java:1337)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.getPotentiallyConvertedSimpleRead(MappingMongoConverter.java:1311)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$DefaultConversionContext.convert(MappingMongoConverter.java:2371)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$ConversionContext.convert(MappingMongoConverter.java:2174)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$MongoDbPropertyValueProvider.getPropertyValue(MappingMongoConverter.java:1936)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readProperties(MappingMongoConverter.java:638)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.populateProperties(MappingMongoConverter.java:549)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:527)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readDocument(MappingMongoConverter.java:491)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:427)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:423)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:120)
at org.springframework.data.mongodb.core.MongoTemplate$ReadDocumentCallback.doWith(MongoTemplate.java:3326)
at org.springframework.data.mongodb.core.MongoTemplate.executeFindOneInternal(MongoTemplate.java:2940)
at org.springframework.data.mongodb.core.MongoTemplate.doFindOne(MongoTemplate.java:2618)
at org.springframework.data.mongodb.core.MongoTemplate.doFindOne(MongoTemplate.java:2588)
at org.springframework.data.mongodb.core.MongoTemplate.findById(MongoTemplate.java:922)
at com.package.myapp.services.mapper.ReferenceMapper.map(ReferenceMapper.java:26)
at com.package.myapp.services.mapper.InstallationMapperImpl.toEntity(InstallationMapperImpl.java:102)
When asking the conversion, the findById works correctly and retrieve the object and the nested one. It fails when the request is for 2nd level nested object, where the ObjectId is retrieved but cannot be converted and fails.
I'm answering myself because I found a solution suited for my problem.
I only needed the entity object with the id, so I wrote a converter:
public class ObjectIdToSubjectConverter implements Converter<ObjectId, Subject> {
#Override
public Subject convert(ObjectId source) {
return Subject.builder().id(source.toString()).build();
}
}
And register it as a bean:
#Configuration
public class MongoConfig {
#Bean
public MongoCustomConversions mongoCustomConversions() {
return new MongoCustomConversions(Collections.singletonList(new ObjectIdToSubjectConverter());
}
}

Spring Jpa Specification unable to locate attribute in sub classes

so I have following hierarchy of entities:
#MappedSuperClass
public abstract class BaseEntity {
private Long id;
private Date createAt;
private Date updateAt;
}
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class Post extends BaseEntity {
private String creatorId;
private String title;
private String content;
}
#Entity
public class Article extends Post {
private String category; // article has category
}
#Entity
public class Journal extends Post {
private Date expiration; // journal has expiration
}
now when I use Spring Jpa Specification to query for articles with certain category, it won't work:
// define specification
public class ArticleSpecifications {
public static Specification<Article> withCategory(String category) {
(root, query, criteriaBuilder) ->
criteriaBuilder.equal(root.get("category"), category)
}
}
// repository
public interface PostRepository<T extends Post> extends JpaRepository<T, Long>, JpaSpecificationExecutor<T> { ... }
// usage: in some service class
#Autowired
private PostRepository<Article> articleRepository;
...
public void someMethod {
...
// error here
articleRepository.findAll(ArticleSpecifications.withCategory("news"), pageable);
...
}
Error message:
java.lang.IllegalArgumentException: Unable to locate Attribute with the the given name [category] on this ManagedType [com.gladdev.galahad.base.BaseEntity]
Just trying to understand here why it tries to look up "category" in BaseEntity.
Every Specification accessing attributes defined in Post works just fine.
Is it some spring jpa specification bug or I missed something?
You can use the CriteriaBuilder.treat() method.
In Criteria API, downcasting an entity to a subclass entity can be performed by using one of the CriteriaBuilder.treat() methods:
See https://docs.oracle.com/javaee/7/api/javax/persistence/criteria/CriteriaBuilder.html#treat-javax.persistence.criteria.Root-java.lang.Class-
public static Specification<Article> withCategory(String category) {
return (root, query, criteriaBuilder) -> {
Root<Article> article = criteriaBuilder.treat(root, Article.class);
return criteriaBuilder.equal(article.get("category"), category);
};
}

Create multiple beans of the same Class in a dynamic and lazy manner

#ConfigurationProperties("person")
public class PersonConfiguration {
private List<Details> details = new ArrayList<>();
//getters and setters and tostring
public static class Details {
private String key;
private String company;
private String family;
//getters and setters and tostring
}
}
public class Person {
public String key;
public SomeClass obj;
//constructor
//getter, setter and tostring
}
#Component
public class PersonFactory {
#Autowired
PersonConfiguration personConfig;
public Person getPerson(String key) {
//if key exists in PersonConfiguration
//create SomeClass based on family and compnay details for corresponding key
//create Person bean using key and SomeClass.
}
}
How can i create multiple beans of Person in the above class in lazy manner only when requested based on the different keys in the request.
Basically have a configuration and factory class. Based on an incoming request need to check if it exists in the configuration and if it does, need to create an object of some type using factory class.

Select one column using Spring Data JPA

Does anyone have any idea how to get a single column using Spring Data JPA? I created a repository like below in my Spring Boot project, but always get the {"cause":null,"message":"PersistentEntity must not be null!"} error when accessing the Restful URL.
#RepositoryRestResource(collectionResourceRel = "users", path = "users")
public interface UsersRepository extends CrudRepository<Users, Integer> {
#Query("SELECT u.userName FROM Users u")
public List<String> getUserName();
}
Then if I access the Restful URL like ../users/search/getUserName, I get the error:
{"cause":null,"message":"PersistentEntity must not be null!"}
Create a Projection interface
public interface UserNameOnly {
String getUserName();
}
Then in your repository interface return that type instead of the user type
public interface UserRepository<User> extends JpaRepository<User,String> {
List<UsernameOnly> findNamesByUserNameNotNull();
}
The get method in the projection interface must match a get method of the defined type on the JPA repository, in this case User.
The "findBySomePropertyOnTheObjectThatIsNotNull" allows you to get a List of the entities (as opposed to an Iterable) based on some criteria, which for a findAll can simply be if the unique identifier (or any other NonNull field) is not null.
Concept is : In your entity class create a constructor with only required instant variables. And use that constructor in the repository method shown below.
Lets say you have a interface Repository like below
Repository implementation:
public interface UserRepository<User> extends JpaRepository<User,String>
{
#Query(value = "select new com.org.User(usr.userId) from User usr where usr.name(:name)")
List<User> findUserIdAlone(#Param("name") String user);
}
In Controller
#RestController
public class UserController
{
#Autowired
private UserRepository<User> userRepository;
#Res
public ResponseEntity<User> getUser(#PathVariable("usrname") String userName)
{
User resultUser = usrRepository.findUserIdAlone(userName);
return ResponseEntity.ok(resultUser);
}
}
public class User
{
private String userId,userName;
public User(String userId)
{
this.userId=userId;
}
// setter and getters goes here
}
This Works for me.
public interface UserDataRepository extends JpaRepository<UserData, Long> {
#Query(value = "SELECT emp_name FROM user_data", nativeQuery = true)
public List<Object[]> findEmp_name();
}
System.out.println("data"+ userDataRepository.findEmp_name());
The above line gave me this result :
data[abhijeet, abhijeet1, abhijeet2, abhijeet3, abhijeet4, abhijeet5]
If you want to only return a single column you should look at Projections and Excerpts which will allow you to filter specific columns and other things that are usefule.
If you need list all of the users, try select userName from Users, if you need one user use "where" look at spring data JPA http://docs.spring.io/spring-data/jpa/docs/current/reference/html/ , try change CrudRepository to JpaRepository
It is possible to provide custom implementations of methods in a Spring Data JPA repository, which enables complete control on queries and return types. The approach is as follows:
Define an interface with the desired method signatures.
Implement the interface to achieve the desired behavior.
Have the Repository extend both JpaRepository and the custom interface.
Here is a working example that uses JpaRepository, assuming a user_table with two columns, user_id and user_name.
UserEntity class in model package:
#Entity
#Table(name = "user_table")
public class UserEntity {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "user_id")
private Long userId;
#Column(name = "user_name")
private String userName;
protected UserEntity() {}
public UserEntity(String userName) {
this.userName = userName;
// standard getters and setters
}
Define interface for the custom repository in the repository package:
public interface UserCustomRepository {
List<String> findUserNames();
}
Provide implementation class for the custom interface in the repository package:
public class UserCustomRepositoryImpl implements UserCustomRepository {
// Spring auto configures a DataSource and JdbcTemplate
// based on the application.properties file. We can use
// autowiring to get a reference to it.
JdbcTemplate jdbcTemplate;
#Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
// Now our custom implementation can use the JdbcTemplate
// to perform JPQL queries and return basic datatypes.
#Override
public List<String> findUserNames() throws DataAccessException {
String sql = "SELECT user_name FROM user_table";
return jdbcTemplate.queryForList(sql, String.class);
}
}
Finally, we just need to have the UserRepository extend both JpaRepository and the custom interface we just implemented.
public interface UserRepository extends JpaRepository<UserEntity, Long>, UserCustomRepository {}
Simple test class with junit 5 (assuming the database is initially empty):
#SpringBootTest
class UserRepositoryTest {
private static final String JANE = "Jane";
private static final String JOE = "Joe";
#Autowired
UserRepository repo;
#Test
void shouldFindUserNames() {
UserEntity jane = new UserEntity(JANE);
UserEntity joe = new UserEntity(JOE);
repo.saveAndFlush(jane);
repo.saveAndFlush(joe);
List<UserEntity> users = repo.findAll();
assertEquals(2, users.size());
List<String> names = repo.findUserNames();
assertEquals(2, names.size());
assertTrue(names.contains(JANE));
assertTrue(names.contains(JOE));
}
}

Spring: Method Injection Lookup How to use it?

Can I use Method Injection Lookup -- with a entity class?.I use Spring+JPA+Hibernate. This allow to inject a prototype bean into a singleton bean.Is this also possible with entity beans?A is prototype scoped bean.I want to put A(#Entity) into a class B (ex. DAO) with scope=singleton.Thanks
#Entity
public class A(){
private String name;
private String surname;
...//get and set
}//A
public interface DAO{
public void method();
}//DAO
public class DAOImpl implements DAO{
private A object_a;
public void method(){
//In this method I use everytime a new istance of A
}//method
}//DAOImpl
You can use #Embedded to include your sub bean, and use in your sql.
#Entity
public class User(){
private String name;
#Embedded
private Address address;
#Bean(scope=DefaultScopes.PROTOTYPE)
public User() {
}
...//get and set
}
#Entity
public class Address(){
private String name;
...//get and set
}
public interface UserRepository extends JpaRepository<User, Long> {
#Query(value = "select u from users u where u.address.name = :addressName")
List<Blog> findUserByAddress(#Param("addressName") String addressName);
}

Resources