Spring Boot controllers are returning a 404 - spring

Spring Boot controllers are returning a 404 on all endpoints
trying to get basic controller returning data
Package structure is setup correctly all packages are sub packages of the main package
annotations look fine been using spring casually for a bit but im clueless im expecting at least hello world im assuming its some spring garbage goin on with it not finding the bean idk no luck finding anything out of conventional configuration. plz help thanks
package com.bookieburglar.api.services;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
//#EnableJpaRepositories(basePackages = "com.bookieburlgar.api.services")
#ComponentScan(basePackages = "com.bookieburglar.api.services")
#SpringBootApplication
public class BookieBurglarApplication {
public static void main(String[] args) {
SpringApplication.run(BookieBurglarApplication.class, args);
}
}
Odds.java
package com.bookieburglar.api.services.models;
import java.util.List;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
public class Odds {
#Id
#JsonProperty("id")
private String id;
#JsonProperty("sport_key")
private String sport_key;
#JsonProperty("sport_title")
private String sport_title;
#JsonProperty("commence_time")
private String commence_time;
#JsonProperty("home_team")
private String home_team;
#JsonProperty("away_team")
private String away_team;
#JsonProperty("bookmakers")
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "odds_id")
private List<Bookmaker> bookmakers;
public Odds(String id, String sportKey, String sportTitle, String commenceTime,
String homeTeam, String awayTeam, List<Bookmaker> bookmakers) {
this.id = id;
this.sport_key = sportKey;
this.sport_title = sportTitle;
this.commence_time = commenceTime;
this.home_team = homeTeam;
this.away_team = awayTeam;
this.bookmakers = bookmakers;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getSportKey() {
return sport_key;
}
public void setSportKey(String sportKey) {
this.sport_key = sportKey;
}
public String getSportTitle() {
return sport_title;
}
public void setSportTitle(String sportTitle) {
this.sport_title = sportTitle;
}
public String getCommenceTime() {
return commence_time;
}
public void setCommenceTime(String commenceTime) {
this.commence_time = commenceTime;
}
public String getHomeTeam() {
return home_team;
}
public void setHomeTeam(String homeTeam) {
this.home_team = homeTeam;
}
public String getAwayTeam() {
return away_team;
}
public void setAwayTeam(String awayTeam) {
this.away_team = awayTeam;
}
public List<Bookmaker> getBookmakers() {
return bookmakers;
}
public void setBookmakers(List<Bookmaker> bookmakers) {
this.bookmakers = bookmakers;
}
}
OddsController
package com.bookieburglar.api.services.controllers;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.MediaType;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.bookieburglar.api.services.services.OddsAPIService;
import com.bookieburglar.api.services.services.OddsService;
import com.bookieburglar.api.services.models.Odds;
import com.bookieburglar.api.services.repositories.OddsRepository;
#RestController
#RequestMapping("/Odds")
public class OddsController {
#Autowired
private OddsService oddsService;
#Autowired
private OddsAPIService oddsAPIService;
#GetMapping("/")
public String getOdds() {
return "WORLD";
//return (List<Odds>) OddsRepository.findAll();
}
// #GetMapping("/{id}")
// public Odds getOdds(#PathVariable String id) {
// return OddsRepository.findById(id).orElse(null);
// }
#PostMapping("/create")
public Odds createOdds(#RequestBody Odds Odds) {
System.out.println("frthoo");
return oddsService.saveOdds(Odds);
}
#GetMapping("/refresh")
#ResponseBody
public String refreshOdds() {
System.out.println("ttgb5");
//return oddsAPIService.refreshOdds();
return "yoo";
}
// #PutMapping("/{id}")
// public Odds updateOdds(#PathVariable String id, #RequestBody Odds Odds) {
// Odds.setId(id);
// return OddsRepository.save(Odds);
// }
//
// #DeleteMapping("/{id}")
// public void deleteOdds(#PathVariable String id) {
// OddsRepository.deleteById(id);
// }
}
OddsServices
package com.bookieburglar.api.services.services;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.bookieburglar.api.services.models.Odds;
import com.bookieburglar.api.services.repositories.OddsRepository;
#Service
public class OddsService {
#Autowired
private OddsRepository oddsRepository;
public List<Odds> getAllOdds() {
return (List<Odds>) oddsRepository.findAll();
}
public Optional<Odds> findOddsById(String id) {
return oddsRepository.findById(id);
}
// public List<Odds> findOddsBySportTitle(String sportTitle) {
// return oddsRepository.findBySportTitle(sportTitle);
// }
//
// public List<Odds> findOddsByHomeTeam(String homeTeam) {
// return oddsRepository.findByHomeTeam(homeTeam);
// }
//
// public List<Odds> findOddsByAwayTeam(String awayTeam) {
// return oddsRepository.findByAwayTeam(awayTeam);
// }
public Odds saveOdds(Odds odds) {
return oddsRepository.save(odds);
}
public void deleteOdds(String id) {
oddsRepository.deleteById(id);
}
}
OddsRepository
package com.bookieburglar.api.services.repositories;
import java.util.List;
import org.springframework.stereotype.Repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.bookieburglar.api.services.models.Odds;
#Repository
public interface OddsRepository extends JpaRepository<Odds, String> {
List<Odds> findAll();
// additional methods can be defined here, for example, to search for odds by sport key or teams
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.bookieburglar.api</groupId>
<artifactId>services</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Bookie Burglar</name>
<description>API for BookieBurglar</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.persistence/javax.persistence-api -->
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>javax.persistence-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

In #ComponentScan annotation. Package name is incorrect.
#ComponentScan(basePackages = "com.bookieburlgar.api.services")
It should be
#ComponentScan(basePackages = "com.bookieburglar.api.services")

Related

Unable to find a MessageBodyReader - RestEasy / Quarkus

I don't know what to do to fix it. When accessing localhost:8080/pets/findByStatusLocal is returning the following error.
Error
Error id 9175b683-f00f-4a8d-9246-a30eb0f8865f-5, javax.ws.rs.ProcessingException: RESTEASY003145: Unable to find a MessageBodyReader of content-type application/json and type interface java.util.List
Pet.java
package org.test;
public class Pet {
private Long id;
private String name;
public Pet() {
this(null, null);
}
public Pet(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
application.properties
org.test.PetService/mp-rest/url=https://petstore.swagger.io/v2/
org.test.PetService/mp-rest/scope=javax.inject.Singleton
PetService.java
package org.test;
import java.util.List;
import java.util.concurrent.CompletionStage;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
#Path("/pet")
#RegisterRestClient
public interface PetService {
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("findByStatus")
public List<Pet> findByStatus(#QueryParam("status") String status);
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("findByStatus")
public CompletionStage<List<Pet>> findByStatusAsync(#QueryParam("status") String status);
}
PetResource.java
package org.test;
import java.util.List;
import java.util.concurrent.CompletionStage;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import org.eclipse.microprofile.rest.client.inject.RestClient;
#Path("/pets")
public class PetResource {
#Inject
#RestClient
PetService petService;
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("findByStatusLocal")
public List<Pet> methodname(#QueryParam("status") String status) {
return petService.findByStatus(status);
}
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("findByStatusLocalAsync")
public CompletionStage<List<Pet>> async(#QueryParam("status") String status) {
return petService.findByStatusAsync(status);
}
}
Is it an error in the source code or could it be in the project configuration?
Thank you very much
*** I made the correction - pom.xml:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client</artifactId>
</dependency>
You need the quarkus-resteasy-reactive-jackson to write (and read) JSON object. Alternatively, you can also use the -jsonb extension. However, I highly recommend Jackson in this case as it offers many more features.
So, in your pom.xml file, add:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-jackson</artifactId>
</dependency>

Unable to map collection com.bank.model.Customer.account I got this error while running my test case

I got Unable to map collection com.bank.model.Customer.account error while running my controller test case..please give a solution.
I using OneTOMany association b/w customer and Account, here I used List to store the accounts.
I don't know why got this type error I am going share that error too
My controller test
'''
package com.bank.controller;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import com.bank.model.Account;
import com.bank.model.Customer;
import com.bank.repository.AccountRepository;
import com.bank.service.AccountService;
import com.bank.service.CustomerService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class testController {
#Autowired
private MockMvc mockMvc;
#Autowired
private WebApplicationContext context;
ObjectMapper objectMapper=new ObjectMapper();
#MockBean
private AccountService accountService;
#MockBean
private CustomerService customerService;
#Before
public void setUp() {
mockMvc=MockMvcBuilders.webAppContextSetup(context).build();
}
#Test
public void testCreateCustomer() throws Exception {
Account account=new Account();
account.setAccountNumber(456);
account.setAccountType("savings");
account.setBalance(2000.0);
Customer customer=new Customer();
customer.setCustomerId(1);
customer.setCustomerName("Manasa");
List<Account> acc=new ArrayList<>();
acc.add(account);
String inputInJson=objectMapper.writeValueAsString(customer);
String URI="/customer/createCust";
Mockito.when(customerService.customerCreate(Mockito.any(Customer.class))).thenReturn(customer);
RequestBuilder requestBuilder=MockMvcRequestBuilders
.post(URI)
.accept(MediaType.APPLICATION_JSON).content(inputInJson)
.contentType(MediaType.APPLICATION_JSON);
MvcResult result=mockMvc.perform(requestBuilder).andReturn();
MockHttpServletResponse response=result.getResponse();
String outputInJson=response.getContentAsString();
assertThat(outputInJson).isEqualTo(outputInJson);
assertEquals(HttpStatus.OK.value(),response.getStatus()-1);
}
'''
My model are
'''
package com.bank.model;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.hibernate.annotations.Proxy;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
#Entity
#Table(name="Customer")
#Proxy(lazy = false)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Integer customerId;
#Column(name="customerName")
String customerName;
#OneToMany( targetEntity=Customer.class,cascade = CascadeType.ALL)
#JoinColumn(name="ca_fk",referencedColumnName = "accountNumber")
private List<Account> account=new ArrayList<>();
public Customer() {
super();
}
public Customer(String customerName, List<Account> account) {
super();
this.customerName = customerName;
this.account = account;
}
public Integer getCustomerId() {
return customerId;
}
public void setCustomerId(Integer customerId) {
this.customerId = customerId;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
public List<Account> getAccount() {
return account;
}
public void setAccount(List<Account> account) {
this.account = account;
}
#Override
public String toString() {
return "Customer [customerId=" + customerId + ", customerName=" + customerName + ", account=" + account + "]";
}
}
'''
'''
package com.bank.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.annotations.Proxy;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
#Entity
#Table(name="Account")
#Proxy(lazy = false)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Account {
#Id
int accountNumber;
#Column(name="balance")
Double balance;
#Column(name="accountType")
String accountType;
//Customer customer;
public Account() {
super();
}
public Account(int accountNumber, Double balance, String accountType, Customer customer) {
super();
this.accountNumber = accountNumber;
this.balance = balance;
this.accountType = accountType;
}
public int getAccountNumber() {
return accountNumber;
}
public void setAccountNumber(int accountNumber) {
this.accountNumber = accountNumber;
}
public Double getBalance() {
return balance;
}
public void setBalance(Double balance) {
this.balance = balance;
}
public String getAccountType() {
return accountType;
}
public void setAccountType(String accountType) {
this.accountType = accountType;
}
public Account(int accountNumber, Double balance, String accountType) {
super();
this.accountNumber = accountNumber;
this.balance = balance;
this.accountType = accountType;
}
}
'''
while running it works fine but issue is only with test cases.
My pom.xml file is
'''
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.bank1</groupId>
<artifactId>bankProjectCapstone-1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>bankProjectCapstone-1</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
'''
so far I got this error
'''
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.AnnotationException: Unable to map collection com.bank.model.Customer.account
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1804) ~[spring-beans-5.3.16.jar:5.3.16]
at
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132) ~[spring-test-5.3.16.jar:5.3.16]
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:124) ~[spring-test-5.3.16.jar:5.3.16]
Caused by: org.hibernate.AnnotationException: Unable to map collection com.bank.model.Customer.account
at org.hibernate.cfg.annotations.CollectionBinder.bindCollectionSecondPass(CollectionBinder.java:1723) ~[hibernate-core-5.6.5.Final.jar:5.6.5.Final]
at org.hibernate.cfg.annotations.CollectionBinder.bindOneToManySecondPass(CollectionBinder.java:944) ~[hibernate-core-5.6.5.Final.jar:5.6.5.Final]
Caused by: org.hibernate.cfg.RecoverableException: Unable to find column with logical name: accountNumber in org.hibernate.mapping.Table(customer) and its related supertables and secondary tables
at org.hibernate.cfg.Ejb3JoinColumn.checkReferencedColumnsType(Ejb3JoinColumn.java:844) ~[hibernate-core-5.6.5.Final.jar:5.6.5.Final]
at org.hibernate.cfg.BinderHelper.createSyntheticPropertyReference(BinderHelper.java:126) ~[hibernate-core-5.6.5.Final.jar:5.6.5.Final]
at org.hibernate.cfg.annotations.CollectionBinder.bindCollectionSecondPass(CollectionBinder.java:1713) ~[hibernate-core-5.6.5.Final.jar:5.6.5.Final]
... 102 common frames omitted
Caused by: org.hibernate.MappingException: Unable to find column with logical name: accountNumber in org.hibernate.mapping.Table(customer) and its related supertables and secondary tables
at org.hibernate.cfg.Ejb3JoinColumn.checkReferencedColumnsType(Ejb3JoinColumn.java:839) ~[hibernate-core-5.6.5.Final.jar:5.6.5.Final]
... 104 common frames omitted
'''

Whitelabel Error Page This application has no explicit mapping

Any guesses where i am wrong..?
package com.yyy.subject.model;
import javax.persistence.Entity;
import javax.persistence.Id;
#Entity
public class Subject {
#Id
private int id;
private String code;
private String name;
private String medium;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMedium() {
return medium;
}
public void setMedium(String medium) {
this.medium = medium;
}
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
package com.yyy.subject.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import com.yyy.subject.model.Subject;
#Repository
public interface SubjectRepository extends JpaRepository<Subject, Integer> {
#Query("select medium,count(medium) from subject group by medium")
public List<Object[]> findTypeAndTypeCount();
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
package com.yyy.subject.service;
import java.util.List;
import org.springframework.stereotype.Service;
import com.yyy.subject.model.Subject;
#Service
public interface SubjectService {
public Subject create(Subject subject);
public Subject update(Subject subject);
public void delete(int id);
public Subject readOne(int id);
public List<Subject> readAll();
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
package com.yyy.subject.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.yyy.subject.model.Subject;
import com.yyy.subject.repository.SubjectRepository;
#Service
public class SubjectServiceImpl implements SubjectService {
#Autowired
private SubjectRepository subjectRepository;
#Override
public Subject create(Subject subject) {
return subjectRepository.save(subject);
}
#Override
public Subject update(Subject subject) {
return subjectRepository.save(subject);
}
#Override
public void delete(int id) {
subjectRepository.deleteById(id);
}
#Override
public Subject readOne(int id) {
return subjectRepository.findById(id).get();
}
#Override
public List<Subject> readAll() {
return (List<Subject>) subjectRepository.findAll();
}
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
package com.yyy.subject.util;
import java.util.List;
import org.springframework.stereotype.Component;
#Component
public interface ReportUtil {
public void generatePieChart(String path,List<Object[]> data);
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
package com.yyy.subject.util;
import java.io.File;
import java.io.IOException;
import java.util.List;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.data.general.DefaultPieDataset;
import org.springframework.stereotype.Component;
#Component
public class ReportUtilImpl implements ReportUtil {
#Override
public void generatePieChart(String path,List<Object[]> data) {
DefaultPieDataset dataset=new DefaultPieDataset();
for (Object[] objects : data) {
dataset.setValue(objects[0].toString(), new Double(objects[1].toString()));
}
JFreeChart chart = ChartFactory.createPieChart3D("Report", dataset);
try {
ChartUtilities.saveChartAsJPEG(new File(path+"pieChart.jpeg"), chart, 300, 300);
} catch (IOException e) {
e.printStackTrace();
}
}
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
package com.yyy.subject.controller;
import java.util.List;
import javax.servlet.ServletContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.yyy.subject.model.Subject;
import com.yyy.subject.repository.SubjectRepository;
import com.yyy.subject.service.SubjectService;
import com.yyy.subject.util.ReportUtil;
#Controller
public class SubjectController {
#Autowired
private SubjectService subjectService;
#Autowired
private SubjectRepository subjectRepository;
#Autowired
private ReportUtil report;
#Autowired
private ServletContext sc;
#RequestMapping("/form33")
public String create(ModelMap model)
{
model.addAttribute("subject", new Subject());
return "page1";
}
#RequestMapping("/form34")
public String create2(ModelMap model,#ModelAttribute("subject") Subject subject)
{
subjectService.create(subject);
model.addAttribute("msz", "Database created with id:"+subject.getId());
return "page1";
}
#RequestMapping("/display")
public String read(ModelMap model)
{
List<Subject> subject = subjectService.readAll();
model.addAttribute("subject", subject);
return "page2";
}
#RequestMapping("/deleteSubject")
public String delete(#RequestParam("id") int id,ModelMap model)
{
subjectService.delete(id);
List<Subject> subject = subjectService.readAll();
model.addAttribute("subject", subject);
return "page2";
}
#RequestMapping("/updateSubject")
public String edit(#RequestParam("id") int id,ModelMap model)
{
Subject subject = subjectService.readOne(id);
model.addAttribute("subject", subject);
return "editpage";
}
#RequestMapping("/updated")
public String update(#ModelAttribute("subject") Subject subject1,ModelMap model)
{
subjectService.update(subject1);
List<Subject> subject = subjectService.readAll();
model.addAttribute("subject", subject);
return "page2";
}
#RequestMapping("/generateReport")
public String generateReport()
{
String path=sc.getRealPath("/");
List<Object[]> data= subjectRepository.findTypeAndTypeCount();
report.generatePieChart(path,data);
return "report";
}
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
package com.yyy.subject;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class YyySubjectApplication {
public static void main(String[] args) {
SpringApplication.run(YyySubjectApplication.class, args);
}
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
spring.mvc.view.prefix=/WEB-INF/view/
spring.mvc.view.suffix=.jsp
server.port=9090
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.yyy.subject</groupId>
<artifactId>YYYSubject</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>YYYSubject</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.hibernate.hql</groupId>
<artifactId>hibernate-hql-parser</artifactId>
<version>1.5.0.Final</version>
</dependency>
<dependency>
<groupId>org.jfree</groupId>
<artifactId>jfreechart</artifactId>
<version>1.0.19</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
<%# page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%# taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%# page isELIgnored="false" %>
<%# taglib prefix = "c" uri = "http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<meta charset="ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
<h2><i>Enter Details</i></h2>
<form:form action="form34" modelAttribute="subject" method="post">
Id:<input type="text" name="id"/><br><br>
Code:<form:input path="code"/><br><br>
Name:<form:input path="name"/><br><br>
Medium:<br>
English<form:radiobutton path="medium" value="ENGLISH"/><br>
Hindi<form:radiobutton path="medium" value="HINDI"/><br>
Punjabi<form:radiobutton path="medium" value="PUNJABI"/>
<br><br>
<input type="submit" value="Submit"/>
<br><br>
${msz}
<br><br>
View All
</form:form>
</body>
</html>
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
ERROR:
Error
creating bean with name 'subjectController': Unsatisfied dependency expressed through field 'subjectService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'subjectServiceImpl': Unsatisfied dependency expressed through field 'subjectRepository'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'subjectRepository': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Validation failed for query for method public abstract java.util.List com.yyy.subject.repository.SubjectRepository.findTypeAndTypeCount()!
Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: subject is not mapped [select medium,count(medium) from subject group by medium]
1) JPQL is case sensitive and 2) you must use aliases:
#Query("select s.medium, count(s.medium) from Subject s group by s.medium")

Spring Data 3.0.5 MongoDB and ElasticSearch Domain Class mixed Annotation

I'm migrating our application from Spring Boot 1.5.9 to version 2.0.0.
In version 1.5.9 we have successfully used mixed Annotations on several Domain Classes e.g:
...
#org.springframework.data.mongodb.core.mapping.Document(collection = "folder")
#org.springframework.data.elasticsearch.annotations.Document(indexName = "folder")
public class Folder {
...
}
The same approach causes probems in Spring Boot 2.0.0. When MongoDB annotatnion #DBRef is used, Spring throws exception while ElasticsearchRepository creation:
java.lang.IllegalStateException: No association found!
Here comes classes and confs
pom.xml
...
<properties>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springfrsamework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
</parent>
...
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
<scope>provided</scope>
</dependency>
</dependencies>
...
Application.java
...
#EnableMongoRepositories("com.hydra.sbmr.repoMongo")
#EnableElasticsearchRepositories("com.hydra.sbmr.repoElastic")
#SpringBootApplication
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Folder.java (Note this #DBRef couses exception)
package com.hydra.sbmr.model;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.DBRef;
#org.springframework.data.mongodb.core.mapping.Document(collection = "folder")
#org.springframework.data.elasticsearch.annotations.Document(indexName = "folder")
public class Folder {
#Id
#Getter #Setter private String id;
// Why MongoDB core mapping #DBRef causes java.lang.IllegalStateException: No association found! exception
// while ElasticsearchRepository creation???
#DBRef
#Getter #Setter private Profile profile;
#Getter #Setter private String something;
}
Profile.java
package com.hydra.sbmr.model;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.Id;
#org.springframework.data.mongodb.core.mapping.Document(collection = "profile")
public class Profile {
#Id
#Getter #Setter private String id;
#Getter #Setter String blah;
}
FolderElasticRepository.java
package com.hydra.sbmr.repoElastic;
import com.hydra.sbmr.model.Folder;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
public interface FolderElasticRepository extends ElasticsearchRepository<Folder, String> {
}
You can find whole mini project on GitHub: https://github.com/hydraesb/sbmr
My question:
Is there any solution that will work with mixed Annotatnions on Domain Classes (mongo and elastic) in Spring Boot 2.0.0???
I have the same issue and the solution that i found is to extends SimpleElasticsearchMappingContext like this :
package com.mypackage;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.elasticsearch.client.Client;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.EntityMapper;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import java.io.IOException;
#Configuration
public class ElasticsearchConfiguration {
#Bean
public ElasticsearchTemplate elasticsearchTemplate(Client client, Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder) {
return new ElasticsearchTemplate(client, new MappingElasticsearchConverter(new CustomElasticsearchMappingContext()),
new CustomEntityMapper(jackson2ObjectMapperBuilder.createXmlMapper(false).build()));
}
public class CustomEntityMapper implements EntityMapper {
private ObjectMapper objectMapper;
public CustomEntityMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
}
#Override
public String mapToString(Object object) throws IOException {
return objectMapper.writeValueAsString(object);
}
#Override
public T mapToObject(String source, Class clazz) throws IOException {
return objectMapper.readValue(source, clazz);
}
}
}
package com.mypackage;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchPersistentEntity;
import org.springframework.data.mapping.model.Property;
import org.springframework.data.mapping.model.SimpleTypeHolder;
public class CustomElasticsearchMappingContext extends SimpleElasticsearchMappingContext {
#Override
protected ElasticsearchPersistentProperty createPersistentProperty(Property property, SimpleElasticsearchPersistentEntity owner, SimpleTypeHolder simpleTypeHolder) {
return new CustomElasticsearchPersistentProperty(property, owner, simpleTypeHolder);
}
}
package com.mypackage;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchPersistentProperty;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.model.Property;
import org.springframework.data.mapping.model.SimpleTypeHolder;
public class CustomElasticsearchPersistentProperty extends SimpleElasticsearchPersistentProperty {
public CustomElasticsearchPersistentProperty(Property property, PersistentEntity owner, SimpleTypeHolder simpleTypeHolder) {
super(property, owner, simpleTypeHolder);
}
#Override
public boolean isAssociation() {
return false;
}
}
I have faced this problem also and I fixed with solution of #ybouraze
#Bean
fun elasticsearchTemplate(client: JestClient, converter: ElasticsearchConverter, builder: Jackson2ObjectMapperBuilder): ElasticsearchOperations {
val entityMapper = CustomEntityMapper(builder.createXmlMapper(false).build())
val mapper = DefaultJestResultsMapper(converter.mappingContext, entityMapper)
return JestElasticsearchTemplate(client, converter, mapper)
}
#Bean
#Primary
fun mappingContext(): SimpleElasticsearchMappingContext {
return MappingContext()
}
#Bean
fun elasticsearchConverter(): ElasticsearchConverter {
return MappingElasticsearchConverter(mappingContext())
}
inner class CustomEntityMapper(private val objectMapper: ObjectMapper) : EntityMapper {
init {
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true)
}
#Throws(IOException::class)
override fun mapToString(`object`: Any): String {
return objectMapper.writeValueAsString(`object`)
}
#Throws(IOException::class)
override fun <T> mapToObject(source: String, clazz: Class<T>): T {
return objectMapper.readValue(source, clazz)
}
}
inner class MappingContext : SimpleElasticsearchMappingContext() {
override fun createPersistentProperty(property: Property, owner: SimpleElasticsearchPersistentEntity<*>, simpleTypeHolder: SimpleTypeHolder): ElasticsearchPersistentProperty {
return PersistentProperty(property, owner, simpleTypeHolder)
}
}
inner class PersistentProperty(property: Property, owner: SimpleElasticsearchPersistentEntity<*>, simpleTypeHolder: SimpleTypeHolder) : SimpleElasticsearchPersistentProperty(property, owner, simpleTypeHolder) {
override fun isAssociation(): Boolean {
return false
}
}

#Transaction not working on multi datasource Spring Boot + MyBatis application

I'm trying to configure Spring Boot + MyBatis application which should work with several datasources. I tried to do it similar to sample here.
Querying and updating data is working, but when I wrote the unit test, I found that #Transactional is not working. Transactions must work between all databases. It means that, if one method with #Transactional make updates on both databases then everything should rollback in case of exception.
It is a sample application for testing purposes and co-workers. After successful configuring the new applications will be configured and developed in similar manner.
Maven:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.aze.mybatis</groupId>
<artifactId>sample-one</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.4.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Below are the configuration classes:
package com.aze.mybatis.sampleone;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
#SpringBootApplication
#EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
Config to database BSCS (Oracle)
package com.aze.mybatis.sampleone.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
#Configuration
#MapperScan(basePackages = "com.aze.mybatis.sampleone.dao", annotationClass = BscsDataSource.class, sqlSessionFactoryRef = BscsDatabaseConfig.SQL_SESSION_FACTORY_NAME)
#EnableTransactionManagement
public class BscsDatabaseConfig {
static final String SQL_SESSION_FACTORY_NAME = "sessionFactoryBscs";
private static final String TX_MANAGER = "txManagerBscs";
#Bean(name = "dataSourceBscs")
#ConfigurationProperties(prefix = "bscs.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = TX_MANAGER)
public PlatformTransactionManager txManagerBscs() {
return new DataSourceTransactionManager(dataSource());
}
#Bean(name = BscsDatabaseConfig.SQL_SESSION_FACTORY_NAME)
public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource());
return sqlSessionFactoryBean.getObject();
}
}
Config to database ONSUBS (Oracle)
package com.aze.mybatis.sampleone.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
#Configuration
#MapperScan(basePackages = "com.aze.mybatis.sampleone.dao", annotationClass = OnsubsDataSource.class, sqlSessionFactoryRef = OnsubsDatabaseConfig.SQL_SESSION_FACTORY_NAME)
#EnableTransactionManagement
public class OnsubsDatabaseConfig {
static final String SQL_SESSION_FACTORY_NAME = "sessionFactoryOnsubs";
private static final String TX_MANAGER = "txManagerOnsubs";
#Bean(name = "dataSourceOnsubs")
#Primary
#ConfigurationProperties(prefix = "onsubs.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = TX_MANAGER)
#Primary
public PlatformTransactionManager txManagerOnsubs() {
return new DataSourceTransactionManager(dataSource());
}
#Bean(name = OnsubsDatabaseConfig.SQL_SESSION_FACTORY_NAME)
#Primary
public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource());
return sqlSessionFactoryBean.getObject();
}
}
Annotation for BSCS:
package com.aze.mybatis.sampleone.config;
public #interface BscsDataSource {
}
and ONSUBS:
package com.aze.mybatis.sampleone.config;
public #interface OnsubsDataSource {
}
Mapper interface that should work with ONSUBS:
package com.aze.mybatis.sampleone.dao;
import com.aze.mybatis.sampleone.config.OnsubsDataSource;
import com.aze.mybatis.sampleone.domain.Payment;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
#Mapper
#OnsubsDataSource
public interface PaymentDao {
Payment getPaymentById(#Param("paymentId") Integer paymentId);
}
and BSCS:
package com.aze.mybatis.sampleone.dao;
import com.aze.mybatis.sampleone.config.BscsDataSource;
import com.aze.mybatis.sampleone.domain.PostpaidBalance;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
#Mapper
#BscsDataSource
public interface PostpaidCustomerDao {
PostpaidBalance getPostpaidBalance(#Param("customerId") Integer customerId);
// BigDecimal amount may be used as second parameter, but I want to show, how to work with two parameters where second is object
void updateDepositAmount(#Param("customerId") Integer customerId, #Param("balance") PostpaidBalance postpaidBalance);
void updateAzFdlLastModUser(#Param("customerId") Integer customerId, #Param("username") String username);
}
Below is a code with #Transactional
package com.aze.mybatis.sampleone.service;
import com.aze.mybatis.sampleone.dao.PaymentDao;
import com.aze.mybatis.sampleone.dao.PostpaidCustomerDao;
import com.aze.mybatis.sampleone.domain.Payment;
import com.aze.mybatis.sampleone.domain.PostpaidBalance;
import com.aze.mybatis.sampleone.exception.DataNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
#Service
public class PaymentServiceImpl implements PaymentService {
private static final String MIN_DEPOSIT_AMOUNT = "150";
#Autowired
private PaymentDao paymentDao;
#Autowired
private PostpaidCustomerDao postpaidCustomerDao;
#Override
public PostpaidBalance getPostpaidBalance(Integer customerId) {
PostpaidBalance balance = postpaidCustomerDao.getPostpaidBalance(customerId);
if (balance == null) {
throw new DataNotFoundException(String.format("Can't find any balance information for customer with customer_id = %d", customerId));
}
return balance;
}
// Note. By default rolling back on RuntimeException and Error but not on checked exceptions
// If you want to rollback on check exception too then add "rollbackFor = Exception.class"
#Transactional(rollbackFor = Exception.class)
#Override
public void updateDepositAmount(Integer customerId, PostpaidBalance postpaidBalance, String username) {
postpaidCustomerDao.updateDepositAmount(customerId, postpaidBalance);
// In case of #Transactional annotation, you can use method from the same class if it doesn't change data on database
PostpaidBalance balance = getPostpaidBalance(customerId);
// This logic is for showing that how the #Transactional annotation works.
// Because of the exception, the previous transaction will rollback
if (balance.getDeposit().compareTo(new BigDecimal(MIN_DEPOSIT_AMOUNT)) == -1) {
throw new IllegalArgumentException("The customer can not have deposit less than " + MIN_DEPOSIT_AMOUNT);
}
// In case of #Transactional annotation, you must not (!!!) use method from the same (!) class if it changes data on database
// That is why, postpaidCustomerDao.updateAzFdlLastModUser() used here instead of this.updateAzFdlLastModUser()
postpaidCustomerDao.updateAzFdlLastModUser(customerId, username);
// If there is no exception, the transaction will commit
}
}
Below is the unit test code:
package com.aze.mybatis.sampleone.service;
import com.aze.mybatis.sampleone.Application;
import com.aze.mybatis.sampleone.config.BscsDatabaseConfig;
import com.aze.mybatis.sampleone.config.OnsubsDatabaseConfig;
import com.aze.mybatis.sampleone.domain.PostpaidBalance;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.math.BigDecimal;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {Application.class, OnsubsDatabaseConfig.class, BscsDatabaseConfig.class})
#TestPropertySource(locations= "classpath:application.properties")
public class PaymentServiceImplTest extends Assert {
// My goal is not to write a full and right unit tests, but just show you examples of working with MyBatis
#Autowired
private PaymentService paymentService;
#Before
public void setUp() throws Exception {
assert paymentService != null;
}
#Test
public void updateDepositAmount() throws Exception {
final int customerId = 4301887; // not recommended way. Just for sample
final String username = "ITCSC";
boolean exceptionRaised = false;
PostpaidBalance balance = paymentService.getPostpaidBalance(customerId);
assertTrue("Find customer with deposit = 0", balance.getDeposit().compareTo(BigDecimal.ZERO) == 0);
balance.setDeposit(BigDecimal.TEN);
try {
paymentService.updateDepositAmount(customerId, balance, username);
} catch (Exception e) {
exceptionRaised = true;
}
assertTrue(exceptionRaised);
balance = paymentService.getPostpaidBalance(customerId);
// We check that transaction was rollback and amount was not changed
assertTrue(balance.getDeposit().compareTo(BigDecimal.ZERO) == 0);
final BigDecimal minDepositAmount = new BigDecimal("150");
balance.setDeposit(minDepositAmount);
paymentService.updateDepositAmount(customerId, balance, username);
balance = paymentService.getPostpaidBalance(customerId);
assertTrue(balance.getDeposit().compareTo(minDepositAmount) != -1);
}
}
Unit test fails on assertTrue(balance.getDeposit().compareTo(BigDecimal.ZERO) == 0);. The I check the database and see that first update postpaidCustomerDao.updateDepositAmount(customerId, postpaidBalance); was not rollback despite the #Transactional annotation.
Please help to solve problem.
If you have multiple TransactionManagers, you'll need to reference the one you want to use for #Transactional using Bean names or Qualifiers.
In your Java Config:
#Bean("myTM")
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(myDatasource());
}
In Services:
#Transactional("myTM")
public void insertWithException(JdbcTemplate jdbcTemplate) {
}
I debug'd my app to see how Spring chooses the TransactionManager. This is done in org.springframework.transaction.interceptor.TransactionAspectSupport#determineTransactionManager
/**
* Determine the specific transaction manager to use for the given transaction.
*/
protected PlatformTransactionManager determineTransactionManager(TransactionAttribute txAttr) {
// Do not attempt to lookup tx manager if no tx attributes are set
if (txAttr == null || this.beanFactory == null) {
return getTransactionManager();
}
String qualifier = txAttr.getQualifier();
if (StringUtils.hasText(qualifier)) {
return determineQualifiedTransactionManager(qualifier);
}
else if (StringUtils.hasText(this.transactionManagerBeanName)) {
return determineQualifiedTransactionManager(this.transactionManagerBeanName);
}
else {
PlatformTransactionManager defaultTransactionManager = getTransactionManager();
if (defaultTransactionManager == null) {
defaultTransactionManager = this.transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY);
if (defaultTransactionManager == null) {
defaultTransactionManager = this.beanFactory.getBean(PlatformTransactionManager.class);
this.transactionManagerCache.putIfAbsent(
DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager);
}
}
return defaultTransactionManager;
}
}
So it's just getting the default primary PlatformTransactionManager bean.

Resources