I would like to load the database type ( Cassandra or MongoDB) in my Springboot Service class based on some condition. For this, I was planning to use #Conditional annotation mentioned in https://sivalabs.in/2016/03/how-springboot-autoconfiguration-magic/.
In my case, I am extending the org.springframework.stereotype. Repository Interface to create the Repository like below.
In my service class, how can I get the instance of Cassandra Type or Mongo Type based on a profile or configuration in application.yml?
Thanks
Ashish
#Repository
public interface CartCassandraRepository extends CrudRepository<com.cassandra.Cart, String> {
}
#Repository
public interface MongoCassandraRepository extends CrudRepository<com.mongo.Cart, String> {
}
Here is my com.cassandra.Cart.java
import org.springframework.data.cassandra.core.cql.PrimaryKeyType;
import org.springframework.data.cassandra.core.mapping.CassandraType;
import org.springframework.data.cassandra.core.mapping.Column;
import org.springframework.data.cassandra.core.mapping.PrimaryKeyColumn;
#Table("Cart")
public class Cart {
#PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED)
#GeneratedValue(strategy = GenerationType.AUTO)
protected String id;
#PrimaryKeyColumn(ordinal = 1, type = PrimaryKeyType.PARTITIONED)
private String userId;
#PrimaryKeyColumn(ordinal = 2, type = PrimaryKeyType.CLUSTERED, ordering = Ordering.DESCENDING)
private String productId;
}
Here is my com.mongo.Cart.java
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field
#Document(collection = "Cart")
public class Cart {
#Id
protected String id;
#Indexed(unique = true)
#Field(value = "UserId")
private String userId;
#Field(value = "ProductId")
private String productId;
}
Here's the Service class
public class CartServiceImpl{
#Autowired
CassandraCartRepository cassandraCartRepository;
#Autowired
MongoCartRepository mongoCartRepository;
public Cart save(CartDTO cart){
// based on configuration or profile load com.mongo.Cart or com.cassandra.Cart
// based on the configuration or profile do mongoCartRepository.save(cart) or cassandraCartRepository.save(cart)
}
}
Related
I am currently learning Spring for GraphQL and am working on an application to do some basic CRUD operations, using the GraphQL API using Spring-boot with JPA and an H2 in-memory database for testing. Now I am trying to write some integration tests using the GraphlQlTester interface. There is a possibility to test importing just the service layer, storing the data in a collection at this layer. Now I want to be able to access the repository layer too to temporarily store and manipulate the test data in the h2 database. I have been researching the documentation online, but unfortunately, I have not found any possible way to approach this particular issue. Perhaps you may have an idea?
Entity Class
#Data
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Builder
#Table(name = "address")
public class Address {
#Id
#GeneratedValue(strategy = IDENTITY)
private long id;
private String street;
private String city;
private int postCode;
}
Controller Class (just the first method)
#Controller
#RequiredArgsConstructor
public class AddressController {
private final AddressService addressService;
private static final String ERROR_MESSAGE = "Address with id %d not found";
#QueryMapping
List<Address> findAllAddresses() {
return addressService.findAll();
}
Service Class (just the first method)
#Service
#RequiredArgsConstructor
public class AddressService {
private final String ERROR_MESSAGE = "Address with id %d not found";
private final AddressRepository addressRepository;
private final ContactInformationRepository contactInformationRepository;
public List<Address> findAll() {
return addressRepository.findAll();
}
Repository
public interface AddressRepository extends JpaRepository<Address, Long> {
}
Integration Test class
import com.soscarlos.dropit.entity.Address;
import com.soscarlos.dropit.repository.AddressRepository;
import com.soscarlos.dropit.service.AddressService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.graphql.GraphQlTest;
import org.springframework.context.annotation.Import;
import org.springframework.graphql.test.tester.GraphQlTester;
#GraphQlTest(AddressController.class)
#Import({AddressService.class, AddressRepository.class})
class AddressControllerTest {
#Autowired
GraphQlTester tester;
#Autowired
AddressService addressService;
#Autowired
AddressRepository addressRepository;
#Test
void findAllAddresses() {
// language=GraphQL
String document = """
query {
findAllAddresses {
id
street
city
postCode
}
}
""";
tester.document(document)
.execute()
.path("findAllAddresses")
.entityList(Address.class)
.hasSize(1);
}
I cannot load the application context because of this error basically
"Error creating bean with name 'com.soscarlos.dropit.repository.AddressRepository': Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.soscarlos.dropit.repository.AddressRepository]: Specified class is an interface"
I'm new to Mapstruct and I'm trying to understand it properly.
What I want to achieve is converting from a DTO String parameter (carModel) to his Entity, retrieve using Service and Repository.
The problem is that Mapper class generated by Mapstruct is trying to inject the Service class with #Autowired annotation, but it's not working. The service is null.
Here's my #Mapper class:
#Mapper(componentModel = "spring", uses = CarModelService.class)
public interface KitMapper extends EntityMapper<KitDTO, Kit> {
KitMapper INSTANCE = Mappers.getMapper(KitMapper.class);
#Mapping(source = "weight", target = "systemWeight")
#Mapping(source = "carModel", target = "carModel")
Kit toEntity(KitDTO kitDTO);
}
public interface EntityMapper<D, E> {
E toEntity(D dto);
List<E> toEntity(List<D> dtoList);
}
The #Service class:
#Service
#Transactional
public class CarModelService {
private final CarModelRepository carModelRepository;
#Transactional(readOnly = true)
public CarModel findByName(String name) {
return carModelRepository.findByName(name).orElse(null);
}
}
The #Repository class:
#Repository
public interface CarModelRepository extends JpaRepository<CarModel, Long> {
Optional<CarModel> findByName(String carModelName);
}
The DTO and Entity classes:
public class KitDTO {
private String id;
private String carModel; // e.g. "Ferrari Monza"
....
}
#Entity
#Table(name = "kit")
public class Kit implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#Column(name = "id")
private Long id;
#ManyToOne
private CarModel carModel;
...
}
#Entity
#Table(name = "car_model")
public class CarModel implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
...
}
The build work properly but the application stop when I try to use the Mapper. It says that carModelService is null.
Here's the mapper generated implementation class:
#Component
public class KitMapperImpl implements KitMapper {
#Autowired // <-- this seems not working
private CarModelService carModelService;
#Override
public Kit toEntity(KitDTO kitDTO) {
if ( kitDTO == null ) {
return null;
}
Kit kit = new Kit();
kit.setSystemWeight( String.valueOf( kitDTO.getWeight() ) );
kit.carModel( carModelService.findByName(kitDTO.getCarModel()) ); // <-- carModelService is null!
// other setters
return kit;
}
}
I've tried many things, using Decorator, #Context, expression, inject the #Mapper class into the #Service class.
I've found many questions but actually no one helped me:
Mapstruct - How can I inject a spring dependency in the Generated Mapper class
#Service Class Not Autowired in org.mapstruct.#Mapper Class
MapStruct mapper not initialized with autowired when debug
Any help would be appreciated! Thanks in advance!
Found the solution!
Instead of calling directly the Mapper method toEntity() from the #RestController class, I injected the mapper in the CarModelService class and created a method that call the mapper.
In this way the flow is:
Controller --> Service --> Mapper
#Service
#Transactional
public class KitService {
private final KitRepository kitRepository;
private final KitSearchRepository kitSearchRepository;
private final KitMapper kitMapper; // <-- mapper declaration
public KitService(KitRepository kitRepository, KitSearchRepository kitSearchRepository, KitMapper kitMapper) {
this.kitRepository = kitRepository;
this.kitSearchRepository = kitSearchRepository;
this.kitMapper = kitMapper; // <-- mapper initilization
}
// here the method which calls mapper
public Kit convertDTOToEntity(KitDTO kitDTO) {
return kitMapper.toEntity(kitDTO);
}
In this way, the generated class by Mapstruct doesn't give error on the CarModelService.
Seems like this approach is the only way to achieve this, create a king of "bridge" between services and mappers.
(You can use also the #Autowired annotation instead of constructor)
Can you please share the error message?
From the information that you shared, I can see the carModel in KitDto is String and in Entity is CarModel class. Not sure how mapstruct's auto generated implementation class implemented this: kit.carModel( carModelService.findByName(kitDTO.getCarModel()) );.
But I would like to share another approach,Don't know this is a best practice or not. In this approach you can create a abstarct class of mapper, in which you can #Autowired repository can manually add those mapping.
I shared the snippet for it. Hopefully this will help you.
#Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
public abstract class ProductMapper {
#Autowired
private CarModelService carModelService;
public abstract Kit convertDTOToEntity(KitDTO kitDTO);
public Kit toEntity(KitDTO kitDTO);
{
Kit kit = convertDTOToEntity(kitDTO);
kit.setCarModel(carModelService.findByName(kitDTO.getCarModel()));
return kit;
}
}
Curious about the other approaches, will follow this thread. We can discuss the best practices
I am beginner with Spring Boot and trying to improve my skills to get new job, so I hope you help me even if the question maybe easy for you as I search a lot and gain nothing.
I need to get by id, but return data is duplicated with only one record, I will show you what I do and the result for more explanation.
In DB side:
I have VW_Prices view in DB and it's data as shown below:
In Spring Boot side:
VW_Prices class is :
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.annotations.Immutable;
#Entity
#Table(name = "VW_PRICES")
public class VW_Prices implements Serializable {
private long dealId;
private Long quotationId;
private Long productPriceForEjada;
private Long productPriceForClient;
private Long productId;
private Long productQuantity;
private String productName;
#Id
#Column(name = "ID")
public long getDealId() {
return dealId;
}
public void setDealId(long dealId) {
this.dealId = dealId;
}
#Column(name = "PRODUCT_QUANTITY")
public Long getProductQuantity() {
return productQuantity;
}
public void setProductQuantity(Long productQuantity) {
this.productQuantity = productQuantity;
}
#Column(name = "PRODUCT_NAME")
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
#Column(name = "PRODUCT_PRICE_FOR_EJADA")
public Long getProductPriceForEjada() {
return productPriceForEjada;
}
public void setProductPriceForEjada(Long productPriceForEjada) {
this.productPriceForEjada = productPriceForEjada;
}
#Column(name = "PRODUCT_PRICE_FOR_CLIENT")
public Long getProductPriceForClient() {
return productPriceForClient;
}
public void setProductPriceForClient(Long productPriceForClient) {
this.productPriceForClient = productPriceForClient;
}
#Column(name = "PRODUCT_ID")
public Long getProductId() {
return productId;
}
public void setProductId(Long productId) {
this.productId = productId;
}
#Column(name = "QUOTATION_ID")
public Long getQuotationId() {
return quotationId;
}
public void setQuotationId(Long quotationId) {
this.quotationId = quotationId;
}
}
and I create VW_PricesRepository
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import springboot.deals_tracker_system.models.VW_Prices;
import springboot.deals_tracker_system.models.VW_Prices_interface;
public interface VW_PricesRepository extends JpaRepository<VW_Prices, Long> {
#Query( nativeQuery = true,value = "SELECT distinct * from VW_Prices v where v.id = :dealID " )
List<VW_Prices> findByDealId( #Param("dealID") Long id);
}
and my in my Service
public List<VW_Prices> findByDealId(Long dealId) {
System.out.println("we are in service");
List<VW_Prices> variableForDebug = VW_pricesRepository.findByDealId(dealId);
for (VW_Prices vw_Prices : variableForDebug) {
System.out.println(vw_Prices.getDealId() + " " + vw_Prices.getProductName());
}
return variableForDebug;
//return VW_pricesRepository.findByDealId(dealId);
}
When I pass dealId = 39 the result comes duplicated and not correct as in below:
how can I get correct data??
The view is made for Quotation Product Table to get product name.
i think the problem is the id annotation you must add GeneratedValue
fro the class:
#Entity
#Table(name = "VW_PRICES")
public class VW_Prices implements Serializable {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private long dealId;
private Long quotationId;
private Long productPriceForEjada;
private Long productPriceForClient;
private Long productId;
private Long productQuantity;
private String productName;
//code..
}
You dont have to use JPQL for this type of queries it's already exist in jpa:
VW_PricesRepository:
public interface VW_PricesRepository extends JpaRepository<VW_Prices, Long> {
}
to get data by id use findById like that:
public VW_Prices findByDealId(Long dealId) {
System.out.println("we are in service");
VW_Prices vw_Prices = VW_pricesRepository.findById(dealId);
System.out.println(vw_Prices.getDealId() + " " +
vw_Prices.getProductName());
}
return vw_Prices;
}
All data should be deleted from VW_Prices table because ids are not unique, try to insert new data with unique id then try the above code
I detect the problem, The view has main table Quotation and I didn't select it's ID and I used ID of the secondary table as the main ID for the View
I just write it if any one Google for such problem
I'm a beginner with spring and I have this little issue. "No property questionId found for type CourseTestCompleteField!" I have 2 model classes that are connected via a one to one join.
That 2 model class are:
package com.example.springboot.models;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
#Entity
#Table(name = "questions")
public class CourseTestQuestion {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="question_id")
private Long id;
#NotBlank
#Column(name = "question_course")
private String questionCourse;
#NotBlank
#Column(name = "question_type")
private String questionType;
public CourseTestQuestion(){
}
public CourseTestQuestion(String questionCourse, String questionType) {
this.questionCourse = questionCourse;
this.questionType = questionType;
}
// public getters and setters for all fields here
}
And:
package com.example.springboot.models;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
#Entity
#Table(name = "quiz_complete_field_questions",
uniqueConstraints = {
#UniqueConstraint(columnNames = "question_id")
}
)
public class CourseTestCompleteField {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Column(name = "question_content")
private String questionContent;
#NotBlank
#Column(name = "answer")
private String answer;
#NotBlank
#Column(name = "points")
private String points;
#NotBlank
#Column(name = "course")
private String course;
#NotBlank
#Column(name = "teacher_username")
private String teacher;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "question_id", referencedColumnName = "question_id")
private CourseTestQuestion courseTestQuestion;
public CourseTestCompleteField(){
}
public CourseTestCompleteField(CourseTestQuestion courseTestQuestion, String question, String answer, String points, String course, String teacher) {
this.courseTestQuestion = courseTestQuestion;
this.questionContent = question;
this.answer = answer;
this.points = points;
this.course = course;
this.teacher = teacher;
}
// public getters and setters for all fields here
}
My repo for both:
package com.example.springboot.repository;
import com.example.springboot.models.Course;
import com.example.springboot.models.CourseTestQuestion;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
#Repository
public interface CourseTestQuestionRepository extends JpaRepository<CourseTestQuestion, Long> {
Optional<CourseTestQuestion> findById(Long id);
Optional<CourseTestQuestion> findByQuestionCourse(String questionCourse);
}
And:
package com.example.springboot.repository;
import com.example.springboot.models.CourseTestCompleteField;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
#Repository
public interface CourseTestCompleteFieldRepository extends JpaRepository<CourseTestCompleteField, Long> {
Optional<CourseTestCompleteField> findById(Long id);
Optional<CourseTestCompleteField> findByQuestionId(Long questionId);
Optional<CourseTestCompleteField> findByCourse(String course);
List<CourseTestCompleteField> findByQuestionContent(String questionContent);
List<CourseTestCompleteField> findByTeacher(String teacher);
Boolean existsByQuestionContent(String questionContent);
}
The problem is with Optional<CourseTestCompleteField> findByQuestionId(Long questionId);but I don't get it why, because in database I have the table for CourseTestCompleteFieldModel with question_id column, and in CourseTestCompleteField I have CourseTestQuestion object. Tho, the table for CourseTestCompleteField has a different name, could be this a problem? I should rename the table to course_test_complete_field?
Can someone help me please? Thank you
Since,This is a query on nested Object. You need to update your query as this.
Optional<CourseTestCompleteField> findByCourseTestQuestion_Id(Long questionId);
This works even without "_"
Optional<CourseTestCompleteField> findByCourseTestQuestionId(Long questionId);
But better to put "_" while accessing nested fields for better readability.
There is no field call questionId in you entity and you have id only.
That's you got error. You can use that findyById(). That's only enough.
If you would like write JPA repository method like findBy..., getBy..., deleteBy...., countBy..., After this you need append exact field name from entity.
For example if you entity have name then can write below methods. findByName(); deleteByName(); countByName();
So try as below.
findBycourseTestQuestion(Object o);
Pass questions object.
Suppose I have two resources Person and Article
#Entity
#Table(name = "person")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long person_id;
private String firstName;
private String lastName;
#OneToMany(mappedBy="person", cascade=CascadeType.ALL)
private List<Article> articles = new ArrayList<>();
}
#Entity
#Table(name="article")
public class Article {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String title;
private String details;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="person_id")
private Person person;
}
I now want to add HATEOAS support to the response of the controller for which I am using org.springframework.hateoas.ResourceAssembler
public class PersonResourceAssembler implements ResourceAssembler<Person, Resource<Person>> {
private EntityLinks entityLinks;
public UserJobResourceAssembler(EntityLinks entityLinks) {
this.entityLinks = entityLinks;
}
#Override
public Resource<Person> toResource(Person entity) {
Resource<UserJob> resource = new Resource<>(entity);
resource.add(
entityLinks.linkFor(Person.class).withSelfRel()),
entityLinks.linkFor(...logic...).withRel("articles")) //here I am hardcoding the relation link name i.e "article"
);
return resource;
}
}
So, in above code the "article" is hardcoded for the link name, but I don't want to do it this way. I want it do in the way Spring-Data-REST handles it i.e for every relationship it auto detects the name of the variable used inside the Entity class e.g articles will be picked from Person and person will be picked from Article.
I have no idea how Spring-Data-REST handles it, but are there any readymade/custom solutions for this requirement?
You can use the reflection API to introspect the entity and find associations. Something like:
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.persistence.OneToMany;
public class AssociationUtility {
public static List<Field> getAssociatedFields(Object entity) {
Stream<Field> fields = Arrays.stream(entity.getClass().getDeclaredFields());
return fields.filter(field -> field.getAnnotation(OneToMany.class)
!= null).collect(Collectors.toList());
}
public static void main(String[] args) {
List<Field> fields = getAssociatedFields(new Customer());
fields.stream().forEach(f -> System.out.println("Make a link for Class: "
+ ((ParameterizedType) f.getGenericType()).getActualTypeArguments()[0]
+ " with rel: " + f.getName()));
System.exit(0);
}
}