javax.servlet.ServletException: Circular view path [products] - spring

I am new in Java Spring 4.0. Here is my project set up.
ProductController
#Controller
public class ProductController {
private ProductService productService;
#Autowired
public void setProductService(ProductService productService) {
this.productService = productService;
}
#RequestMapping("/products")
public String listProducts(Model model){
model.addAttribute("products",productService.listAllProducts());
return "products";
}
#RequestMapping("product/{id}")
public String getProduct(#PathVariable Integer id, Model model){
model.addAttribute("product", productService.getProductById(id));
return "product";
}
#RequestMapping("product/edit/{id}")
public String edit(#PathVariable Integer id, Model model){
model.addAttribute("product",productService.getProductById(id));
return "productform";
}
#RequestMapping("/product/new")
public String newProduct(Model model){
model.addAttribute("product",new Product());
return "productform";
}
#RequestMapping(value = "/product", method = RequestMethod.POST)
public String saveOrUpdateProduct(Product product){
Product saveProduct = productService.saveOrUpdateProduct(product);
return "redirect:/product/" + saveProduct.getId();
}
#RequestMapping("/product/delete/{id}")
public String delete(#PathVariable Integer id){
productService.deleteProduct(id);
return "redirect:/products";
}
}
ProductServiceImpl
#Service
public class ProductServiceImpl implements ProductService {
Map<Integer, Product> products;
public ProductServiceImpl(){
loadProducts();
}
#Override
public List<Product> listAllProducts() {
return new ArrayList<>(products.values());
}
#Override
public Product getProductById(Integer id) {
return products.get(id);
}
#Override
public Product saveOrUpdateProduct(Product product) {
if (product != null){
if (product.getId() == null){
product.setId(getNextKey());
}
products.put(product.getId(), product);
return product;
} else {
throw new RuntimeException("Product Can't be nill");
}
}
#Override
public void deleteProduct(Integer id) {
products.remove(id);
}
private Integer getNextKey(){
return Collections.max(products.keySet()) + 1;
}
private void loadProducts(){
products = new HashMap<>();
Product product1 = new Product();
product1.setId(1);
product1.setDescription("Product 1");
product1.setPrice(new BigDecimal("12.99"));
product1.setImageUrl("http://example.com/product1");
products.put(1,product1);
Product product2 = new Product();
product2.setId(2);
product2.setDescription("Product 2");
product2.setPrice(new BigDecimal("14.99"));
product2.setImageUrl("http://example.com/product2");
products.put(2, product2);
Product product3 = new Product();
product3.setId(3);
product3.setDescription("Product 3");
product3.setPrice(new BigDecimal("34.99"));
product3.setImageUrl("http://example.com/product3");
products.put(3, product3);
Product product4 = new Product();
product4.setId(4);
product4.setDescription("Product 4");
product4.setPrice(new BigDecimal("44.99"));
product4.setImageUrl("http://example.com/product4");
products.put(4, product4);
Product product5 = new Product();
product5.setId(5);
product5.setDescription("Product 5");
product5.setPrice(new BigDecimal("25.99"));
product5.setImageUrl("http://example.com/product5");
products.put(5, product5);
}
}
So, I am injecting a CRUD service to the controller and works good. I can post, delete, display and update data. Now I want to make mockito test. So.
ProductControllerTest
public class ProductControllerTest {
#Mock
private ProductService productService;
#InjectMocks
private ProductController productController;
private MockMvc mockMvc;
#Before
public void setup(){
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(productController).build();
}
#Test
public void testList() throws Exception{
List<Product> products = new ArrayList<>();
products.add(new Product());
products.add(new Product());
when(productService.listAllProducts()).thenReturn((List)products);
mockMvc.perform(get("/products"))
.andExpect(status().isOk())
.andExpect(view().name("products"))
.andExpect(model().attribute("products",hasSize(2)));
}
}
Unfortunately, the test doesn't pass.
javax.servlet.ServletException: Circular view path [products]: would
dispatch back to the current handler URL [/products] again. Check your
ViewResolver setup! (Hint: This may be the result of an unspecified
view, due to default view name generation.)
Why is this happening? I mean I can display the products in my browswer, but the unit test fails.
I am using spring boot and the thymeleaf template.
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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>theo.tziomakas</groupId>
<artifactId>springmvc</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springmvc</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>3.3.5</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>

Please try to add #ResponseBody annotation on methods of ProductController with #RequestMapping as shown below.
#RequestMapping("/products")
#ResponseBody
public String listProducts(Model model){
model.addAttribute("products",productService.listAllProducts());
return "products";
}
Hope it would help.

Related

Duplicate Key Error on saving Reactor Mongo result

I'm seeing the following error when attempting to query and update some records to MongoDB using reactive streams:
org.springframework.dao.DuplicateKeyException: E11000 duplicate key error collection: testtrans.submission index: _id_ dup key: { _id: ObjectId('600b10b2fbac4f4483af3e67') }; nested exception is com.mongodb.MongoWriteException
I'm not sure what I'm doing wrong; How do I go about querying and saving results to the database using reactive mongo in a single transaction?
My service class:
package com.example.reactivemongotransaction.service;
import com.example.reactivemongotransaction.dto.Submission;
import com.example.reactivemongotransaction.repository.SubmissionRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
#Slf4j
#Service
public class SubmissionService {
#Autowired
private SubmissionRepository submissionRepository;
public Mono<Submission> saveSubmission(Submission submission) {
log.debug("saving {}", submission);
return submissionRepository.save(submission);
}
#Transactional
public Flux<Submission> lockSubmissions() {
log.debug("setting all locked");
Flux<Submission> submissionFlux = submissionRepository.findAllByLockedFalse();
return submissionFlux
.map(submission -> submission.setLocked(true))
.flatMap(submission -> submissionRepository.save(submission));
}
}
My config:
#Configuration
#EnableMongoAuditing
#EnableTransactionManagement
public class MongoConfiguration {
#Bean
public ReactiveTransactionManager transactionManager(ReactiveMongoDatabaseFactory dbFactory) {
return new ReactiveMongoTransactionManager(dbFactory);
}
}
Controller:
#RestController
#RequestMapping("/submissions")
public class SubmissionController {
#Autowired
private SubmissionService submissionService;
#PostMapping
public Mono<Submission> saveSubmission(final #RequestBody Submission submission) {
return submissionService.saveSubmission(submission);
}
#GetMapping("/lockall")
public Flux<Submission> lockAll() {
return submissionService.lockSubmissions();
}
}
Model:
#ToString
#Getter
#Setter
#Accessors(chain = true)
#Document(collection = "submission")
#TypeAlias("payload")
public class Submission implements Persistable<String> {
#Id
private String id;
#Field("role_name")
#Indexed(unique = true)
private String role;
#CreatedDate
private ZonedDateTime created;
#LastModifiedDate
private ZonedDateTime updated;
private Boolean deleted;
private Boolean enabled;
private boolean locked;
#Override
#JsonIgnore
public boolean isNew() {
if(getCreated() == null)
return true;
else
return false;
}
}
Repository:
public interface SubmissionRepository extends ReactiveMongoRepository<Submission, String> {
Flux<Submission> findAllByLockedFalse();
}
Main class:
#EnableReactiveMongoRepositories
#SpringBootApplication
public class ReactivemongotransactionApplication {
public static void main(String[] args) {
SpringApplication.run(ReactivemongotransactionApplication.class, args);
}
}
application.yml:
spring:
data:
mongodb:
uri: 'mongodb://localhost:27017/testtrans'
server:
port: 8280
Maven pom:
<?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.4.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>reactivemongotransaction</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>reactivemongotransaction</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<lombok.version>1.18.6</lombok.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
My request is GET http://localhost:8280/submissions/lockall which return 500. And the logs show errors e.g.:
com.mongodb.MongoWriteException: E11000 duplicate key error collection: testtrans.submission index: _id_ dup key: { _id: ObjectId('600b10b2fbac4f4483af3e67') }
at com.mongodb.internal.async.client.AsyncMongoCollectionImpl.lambda$executeSingleWriteRequest$9(AsyncMongoCollectionImpl.java:1075) ~[mongodb-driver-core-4.1.1.jar:na]
The issue was related to the #CreatedDate not being set (see #EnableMongoAuditing and #CreatedDate Auditing not working in Spring Boot 2.4.3).
Resolved by reverting spring-boot-starter-parent to 2.3.5.RELEASE.

Spring REST Controller Not Starting

pom.xml
<groupId>com.ibm.springboot</groupId>
<artifactId>spring-boot-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-boot-api</name>
<description>Spring API DATA</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.14.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<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.apache.derby</groupId>
<artifactId>derby</artifactId>
<scope>runtime</scope>
</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-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
SpringBootApiApplication.java
#SpringBootApplication
#ComponentScan("com.ibm.springboot.*")
#EntityScan("com.ibm.springboot.topics.Topic")
#EnableJpaRepositories("com.ibm.springboot.topics.TopicRepository")
public class SpringBootApiApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootApiApplication.class, args);
}
}
Topic.java
#Entity
public class Topic {
#Id
private String id;
private String name;
private String description;
public Topic(String id, String name, String description) {
super();
this.id = id;
this.name = name;
this.description = description;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
TopicController.java
#RestController
public class TopicController {
#Autowired
TopicService topicService;
#RequestMapping("/topics")
public List<Topic> getAllTopics(){
return topicService.getAllTopics();
}
#RequestMapping(method=RequestMethod.POST,value="/topics")
public void addTopic(#RequestBody Topic topic){
topicService.addTopic(topic);
}
}
TopicRepository.java
#Repository
public interface TopicRepository extends CrudRepository<Topic, String>{
}
TopicService.java
#Service
public class TopicService {
#Autowired
TopicRepository topicRepository;
public List<Topic> getAllTopics(){
List<Topic> topics = new ArrayList<>();
topicRepository.findAll().forEach(topics::add);
return topics;
}
public void addTopic(Topic topic){
// topics.add(topic);
topicRepository.save(topic);
}
}
While I am running the main class , I am getting the following error.
APPLICATION FAILED TO START
Description:
Field topicRepository in com.ibm.springboot.topics.TopicService
required a bean of type 'com.ibm.springboot.topics.TopicRepository'
that could not be found.
Action:
Consider defining a bean of type 'com.ibm.springboot.topics.TopicRepository' in your configuration.
I have checked all the related suggestion given here, but nothing clicked.Please help
Remove the #Repository annotation from your TopicRepository interface.
Should be (no annotation over it):
public interface TopicRepository extends CrudRepository{
}
EDIT
Change your:
#EnableJpaRepositories("com.ibm.springboot.topics.TopicRepository")
To:
#EnableJpaRepositories(basePackageClasses= {com.ibm.springboot.topics.TopicRepository.class})

Spring Boot 5 multiple JUnit JPA test files wiring

I have two tests files in project.
One is testing directly my persistence layer :
#RunWith(SpringRunner.class)
#DataJpaTest
public class UserTests {
#Autowired
private TestEntityManager entityManager;
#Autowired
private UserRepository repository;
#Test
public void whenFindByEmail_thenReturnUser() {
// given
User user = new User("email#email.com", "12345678", "Some Name");
entityManager.persist(user);
entityManager.flush();
// when
User found = repository.findByEmail(user.getEmail());
// then
assertThat(found.getName()).isEqualTo(user.getName());
assertThat(found.getEmail()).isEqualTo(user.getEmail());
assertThat(found.getPasswordHash()).isEqualTo(user.getPasswordHash());
}
}
The other one is testing a service using the persistence layer :
#RunWith(SpringRunner.class)
#DataJpaTest
public class UserServiceTests {
#Autowired
private UserService service;
#Test
public void testSuccessfullUserCreation() {
UserCreationResult res = service.createUser("anything#anything.com", "1234567890", "Test");
assertThat(res).isEqualTo(UserCreationResult.OK);
}
#Test
public void testWrongEmailUserCreation() {
UserCreationResult res = service.createUser("anything#anything", "1234567890", "Test");
assertThat(res).isEqualTo(UserCreationResult.INVALID_EMAIL);
}
#Test
public void testTooShortPasswordUserCreation() {
String shortPassword =
String.join("", Collections.nCopies(UserService.minPasswordLength - 1, "0"));
UserCreationResult res = service.createUser("anything#anything.com", shortPassword, "Test");
assertThat(res).isEqualTo(UserCreationResult.WRONG_PASSWORD_LENGTH);
}
#Test
public void testTooLongPasswordUserCreation() {
String longPassword =
String.join("", Collections.nCopies(UserService.maxPasswordLength + 1, "0"));
UserCreationResult res = service.createUser("anything#anything.com", longPassword, "Test");
assertThat(res).isEqualTo(UserCreationResult.WRONG_PASSWORD_LENGTH);
}
#Test
public void testMaxLengthPasswordUserCreation() {
String maxPassword =
String.join("", Collections.nCopies(UserService.maxPasswordLength, "0"));
UserCreationResult res = service.createUser("anything#anything.com", maxPassword, "Test");
assertThat(res).isEqualTo(UserCreationResult.OK);
}
#Test
public void testMinLengthPasswordUserCreation() {
String minPassword =
String.join("", Collections.nCopies(UserService.minPasswordLength, "0"));
UserCreationResult res = service.createUser("anything#anything.com", minPassword, "Test");
assertThat(res).isEqualTo(UserCreationResult.OK);
}
#Test
public void testReservedEmailUserCreation() {
String email = "email#email.com";
UserCreationResult res = service.createUser(email, "1234567890", "Test");
assertThat(res).isEqualTo(UserCreationResult.OK);
res = service.createUser(email, "1234567890", "Test");
assertThat(res).isEqualTo(UserCreationResult.RESERVED_EMAIL);
}
}
First, my service autowiring wasn't working (UnsatisfiedDependencyException) so I had to add :
#ComponentScan("my.service.package") annotation to the test class.
This made the tests of UserServiceTests work when they were run independently (using eclipse to run only this class).
But when running all the tests of my app (in eclipse or with a good old mvn clean test), I had the same error on the same test class.
I tried adding the same component scan annotation to the other test class (UserTests) then everything worked.
I removed the component scan annotation from UserServiceTests and it still works.
I obviously deduced that the order the tests are executed matters.
Here are my 3 questions, the real one being the last one :
In the first place why do I have to put this component scan annotation even if my class is properly annotated #Service (so should be detected as a bean) ?
How is it that the order of tests matters ?
How can I have multiple JPA tests files that would run with a proper dependency injection independently ?
Here is my service class :
#Service
public class UserService {
#Autowired
private UserRepository repository;
public static final int minPasswordLength = 8;
public static final int maxPasswordLength = 50;
public static enum UserCreationResult {
OK, INVALID_EMAIL, RESERVED_EMAIL, WRONG_PASSWORD_LENGTH, UNKNOWN_ERROR
}
#Transactional
public UserCreationResult createUser(String email, String password, String name) {
if (password.length() < minPasswordLength || password.length() > maxPasswordLength) {
return UserCreationResult.WRONG_PASSWORD_LENGTH;
}
if (!EmailValidator.getInstance().isValid(email)) {
return UserCreationResult.INVALID_EMAIL;
}
final User existingUser = repository.findByEmail(email);
if (existingUser != null) {
return UserCreationResult.RESERVED_EMAIL;
}
final User user = repository.save(new User(email, password, name));
return user == null ? UserCreationResult.UNKNOWN_ERROR : UserCreationResult.OK;
}
}
And there is my 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.somedomain</groupId>
<artifactId>ws-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>My App</name>
<description>My app's description</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</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-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Thanks to this question and answer I was able to make my tests work with proper configuration.
In my UserServiceTests, the service was basically not autowired because that's the expected behavior of #DataJpaTest : it doesn't scan for regular beans.
So I used #SpringBootTest for this class and removed the component scanning in both test classes.
After that, some of my service tests were failing because with #SpringBootTest, the database is not reset after each test.
I added a simple repository cleaning after each service test and everything works fine.
This question still remains :
How is it that the order of tests matters ?
Here are my new test classes :
Service tests :
#RunWith(SpringRunner.class)
#SpringBootTest
public class UserServiceTests {
#Autowired
private UserService service;
#Autowired
private UserRepository repository;
#After
public void cleanUsers() {
repository.deleteAll();
}
#Test
public void testSuccessfullUserCreation() {
UserCreationResult res = service.createUser("anything#anything.com", "1234567890", "Test");
assertThat(res).isEqualTo(UserCreationResult.OK);
}
#Test
public void testWrongEmailUserCreation() {
UserCreationResult res = service.createUser("anything#anything", "1234567890", "Test");
assertThat(res).isEqualTo(UserCreationResult.INVALID_EMAIL);
}
#Test
public void testTooShortPasswordUserCreation() {
String shortPassword =
String.join("", Collections.nCopies(UserService.minPasswordLength - 1, "0"));
UserCreationResult res = service.createUser("anything#anything.com", shortPassword, "Test");
assertThat(res).isEqualTo(UserCreationResult.WRONG_PASSWORD_LENGTH);
}
#Test
public void testTooLongPasswordUserCreation() {
String longPassword =
String.join("", Collections.nCopies(UserService.maxPasswordLength + 1, "0"));
UserCreationResult res = service.createUser("anything#anything.com", longPassword, "Test");
assertThat(res).isEqualTo(UserCreationResult.WRONG_PASSWORD_LENGTH);
}
#Test
public void testMaxLengthPasswordUserCreation() {
String maxPassword =
String.join("", Collections.nCopies(UserService.maxPasswordLength, "0"));
UserCreationResult res = service.createUser("anything#anything.com", maxPassword, "Test");
assertThat(res).isEqualTo(UserCreationResult.OK);
}
#Test
public void testMinLengthPasswordUserCreation() {
String minPassword =
String.join("", Collections.nCopies(UserService.minPasswordLength, "0"));
UserCreationResult res = service.createUser("anything#anything.com", minPassword, "Test");
assertThat(res).isEqualTo(UserCreationResult.OK);
}
#Test
public void testReservedEmailUserCreation() {
String email = "email#email.com";
UserCreationResult res = service.createUser(email, "1234567890", "Test");
assertThat(res).isEqualTo(UserCreationResult.OK);
res = service.createUser(email, "1234567890", "Test");
assertThat(res).isEqualTo(UserCreationResult.RESERVED_EMAIL);
}
}
JPA tests :
#RunWith(SpringRunner.class)
#DataJpaTest
public class UserTests {
#Autowired
private TestEntityManager entityManager;
#Autowired
private UserRepository repository;
#Test
public void whenFindByEmail_thenReturnUser() {
// given
User user = new User("user#user.com", "12345678", "Some Name");
entityManager.persist(user);
entityManager.flush();
// when
User found = repository.findByEmail(user.getEmail());
// then
assertThat(found.getName()).isEqualTo(user.getName());
assertThat(found.getEmail()).isEqualTo(user.getEmail());
assertThat(found.getPasswordHash()).isEqualTo(user.getPasswordHash());
}
}

Springboot mail

This is the first time I'm trying to send mail, using spring-boot-starter-mail.I dont get any error but while sending my mail but i could not see any mail to my TO id.
application.properties
spring.mail.host = smtp.mandrillapp.com
spring.mai.username = lending#vit.in
spring.mail.password = ********
spring.mail.properties.mail.smtp.auth = true
spring.mail.properties.mail.smtp.starttls.enable = true
spring.mail.properties.mail.smtp.socketFactory.fallback = false
My Application Class
#SpringBootApplication
#ComponentScan(basePackages = {"com.mail"})
public class MailServiceApplication extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(MailServiceApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(MailServiceApplication.class, args);
}
#Bean
public ServletRegistrationBean jerseyServlet(){
ServletRegistrationBean register = new ServletRegistrationBean(new ServletContainer(),"/*");
register.addInitParameter(ServletProperties.JAXRS_APPLICATION_CLASS, JerseyInitialization.class.getName());
return register;
}
}
JerseyInitialization.class
#Component
public class JerseyInitialization extends ResourceConfig{
public JerseyInitialization(){
super();
this.packages("com.mail.interfaces");
System.out.println("111");
//register(GenericFilter.class);
// property(ServerProperties.MONITORING_STATISTICS_ENABLED, true);
}
}
Controller Class:
#RestController
#Path("/mail")
public class MailResource {
#Autowired
MailSendInter mailInter;
#GET
#Path("/send")
public String sendMail(){
System.out.println("inside the resource Class");
boolean result = mailInter.send("aravind11081990#gmail.com", "Welcome Mail","Wlcome to the lending system");
if(result){
return "ok";
}else{
return "failure";
}
}
}
Interface Class:
#Service
public interface MailSendInter {
public boolean send(String to, String subject, String body);
}
ImplementationClass:
#Component
public class MailSendImplementation implements MailSendInter{
#Autowired
private JavaMailSender javaMailSender;
#Override
public boolean send(String to, String subject, String body) {
// TODO Auto-generated method stub
boolean result = false;
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
try{
MimeMessageHelper helper ;
helper = new MimeMessageHelper(mimeMessage, true);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(body, true);
javaMailSender.send(mimeMessage);
result = true;
}catch(MessagingException mex){
result = false;
System.err.println("Expection"+mex);
}
return result;
}
}
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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mail</groupId>
<artifactId>MailService</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>MailService</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
I'm using Mandrill for sending mail
URL:https://www.mandrill.com
Username : lending#vit.in
Password: ********
Host : smtp.mandrillapp.com
Port : 587
SMTP Username : lending#vit.in
SMTP Password : ********
Can some one help to figure out the error

Spring Boot + Spring Data JPA + Transactions not working properly

I have created a Spring Boot application using 1.2.0 version with spring-boot-starter-data-jpa and I am using MySQL.
I have configured my MySQL properties in application.properties file correctly.
I have a simple JPA Entity, Spring Data JPA Repository and a Service as follows:
#Entity
class Person
{
#Id #GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String name;
//setters & getters
}
#Repository
public interface PersonRepository extends JpaRepository<Person, Integer>{
}
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
#Service
#Transactional
class PersonService
{
#Autowired PersonRepository personRepository;
#Transactional
void save(List<Person> persons){
for (Person person : persons) {
if("xxx".equals(person.getName())){
throw new RuntimeException("boooom!!!");
}
personRepository.save(person);
}
}
}
I have the following JUnit test:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
public class ApplicationTests {
#Autowired
PersonService personService;
#Test
public void test_logging() {
List<Person> persons = new ArrayList<Person>();
persons.add(new Person(null,"abcd"));
persons.add(new Person(null,"xyz"));
persons.add(new Person(null,"xxx"));
persons.add(new Person(null,"pqr"));
personService.save(persons);
}
}
The expectation here is it should not insert any records into PERSON table as it will throw Exception while inserting 3rd person object.
But it is not getting rolled back, first 2 records are getting inserted and committed.
Then I thought of quickly try with JPA EntityManager.
#PersistenceContext
private EntityManager em;
em.save(person);
Then I am getting javax.persistence.TransactionRequiredException: No transactional EntityManager available Exception.
After googling for sometime I encounter this JIRA thread https://jira.spring.io/browse/SPR-11923 on the same topic.
Then I updated the Spring Boot version to 1.1.2 to get Spring version older than 4.0.6.
Then em.save(person) working as expected and Transaction is working fine (means it is rollbacking all the db inserts when RuntimeException occurred).
But even with Spring 4.0.5 + Spring Data JPA 1.6.0 versions transactions are not working when personRepository.save(person) is used instead of em.persist(person).
It seems Spring Data JPA repositories are committing the transactions.
What am I missing? How to make Service level #Transactional annotations work?
PS:
Maven 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sivalabs</groupId>
<artifactId>springboot-data-jpa</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springboot-data-jpa</name>
<description>Spring Boot Hello World</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.0.RELEASE</version>
<relativePath />
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>com.sivalabs.springboot.Application</start-class>
<java.version>1.7</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</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>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
</project>
Application.java
#EnableAutoConfiguration
#Configuration
#ComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Change Transactional annotation to
#Transactional(rollbackFor=Exception.class)
From comment by #m-deinum:
Make your PersonService public as well as the method you are calling.
This seems to have done the trick for several users. The same thing is also covered in this answer, citing manual saying:
When using proxies, you should apply the #Transactional annotation
only to methods with public visibility. If you do annotate protected,
private or package-visible methods with the #Transactional annotation,
no error is raised, but the annotated method does not exhibit the
configured transactional settings. Consider the use of AspectJ (see
below) if you need to annotate non-public methods.
I tested this with SpringBoot v1.2.0 and v1.5.2 and all is working as expected, you don't need to use entity manager neither #Transactional(rollbackFor=Exception.class).
I could not see which part you misconfigured, all seems fine at first glance, so I am just posting all my config as reference with more updated annotations and H2 embedded memory DB:
application.properties
# Embedded memory database
spring.jpa.hibernate.ddl-auto=create
spring.datasource.url=jdbc:h2:~/test;AUTO_SERVER=TRUE
pom.xml
<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.demo</groupId>
<artifactId>jpaDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
</project>
Person.java
#Entity
public class Person
{
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private Integer id;
private String name;
public Person() {}
public Person(String name) {this.name = name;}
public Integer getId() {return id;}
public void setId(Integer id) {this.id = id;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
}
PersonRepository.java
#Repository
public interface PersonRepository extends JpaRepository<Person, Integer> {}
PersonService.java
#Service
public class PersonService
{
#Autowired
PersonRepository personRepository;
#Transactional
public void saveTransactional(List<Person> persons){
savePersons(persons);
}
public void saveNonTransactional(List<Person> persons){
savePersons(persons);
}
private void savePersons(List<Person> persons){
for (Person person : persons) {
if("xxx".equals(person.getName())){
throw new RuntimeException("boooom!!!");
}
personRepository.save(person);
}
}
}
Application.java
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
PersonServiceTest.java
#RunWith(SpringRunner.class)
#SpringBootTest
public class PersonServiceTest {
#Autowired
PersonService personService;
#Autowired
PersonRepository personRepository;
#Test
public void person_table_size_1() {
List<Person> persons = getPersons();
try {personService.saveNonTransactional(persons);}
catch (RuntimeException e) {}
List<Person> personsOnDB = personRepository.findAll();
assertEquals(1, personsOnDB.size());
}
#Test
public void person_table_size_0() {
List<Person> persons = getPersons();
try {personService.saveTransactional(persons);}
catch (RuntimeException e) {}
List<Person> personsOnDB = personRepository.findAll();
assertEquals(0, personsOnDB.size());
}
public List<Person> getPersons(){
List<Person> persons = new ArrayList() {{
add(new Person("aaa"));
add(new Person("xxx"));
add(new Person("sss"));
}};
return persons;
}
}
*The database is cleared and re-initialized for each test so that we always validate against a known state

Resources