From 7816dda578dc620dae34682d00035a556c478f67 Mon Sep 17 00:00:00 2001 From: tboychuk Date: Sat, 22 Sep 2018 22:00:44 +0300 Subject: [PATCH 1/8] Complete the exercise "Hello ApplicationContext" --- .../src/main/java/com/bobocode/config/AppConfig.java | 9 +++++++++ .../src/main/java/com/bobocode/dao/FakeAccountDao.java | 3 +++ .../main/java/com/bobocode/service/AccountService.java | 2 ++ 3 files changed, 14 insertions(+) diff --git a/hello-application-context/src/main/java/com/bobocode/config/AppConfig.java b/hello-application-context/src/main/java/com/bobocode/config/AppConfig.java index a650020..d87dc9d 100644 --- a/hello-application-context/src/main/java/com/bobocode/config/AppConfig.java +++ b/hello-application-context/src/main/java/com/bobocode/config/AppConfig.java @@ -1,6 +1,9 @@ package com.bobocode.config; import com.bobocode.TestDataGenerator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; /** * This class application context configuration. @@ -9,6 +12,12 @@ * todo: enable component scanning for dao and service packages * todo: configure a bean of type {@link TestDataGenerator} with name "dataGenerator". Don't specify bean name explicitly */ +@Configuration +@ComponentScan(basePackages = {"com.bobocode.dao","com.bobocode.service"}) public class AppConfig { + @Bean + public TestDataGenerator dataGenerator() { + return new TestDataGenerator(); + } } diff --git a/hello-application-context/src/main/java/com/bobocode/dao/FakeAccountDao.java b/hello-application-context/src/main/java/com/bobocode/dao/FakeAccountDao.java index a2e6b3e..bbdb6b4 100644 --- a/hello-application-context/src/main/java/com/bobocode/dao/FakeAccountDao.java +++ b/hello-application-context/src/main/java/com/bobocode/dao/FakeAccountDao.java @@ -3,6 +3,7 @@ import com.bobocode.TestDataGenerator; import com.bobocode.model.Account; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; import java.util.List; import java.util.stream.Stream; @@ -15,9 +16,11 @@ * todo: configure this class as Spring component with bean name "accountDao" * todo: use explicit (with {@link Autowired} annotation) constructor-based dependency injection */ +@Component("accountDao") public class FakeAccountDao implements AccountDao { private List accounts; + @Autowired public FakeAccountDao(TestDataGenerator testDataGenerator) { this.accounts = Stream.generate(testDataGenerator::generateAccount) .limit(20) diff --git a/hello-application-context/src/main/java/com/bobocode/service/AccountService.java b/hello-application-context/src/main/java/com/bobocode/service/AccountService.java index 1b036dd..5bbea2f 100644 --- a/hello-application-context/src/main/java/com/bobocode/service/AccountService.java +++ b/hello-application-context/src/main/java/com/bobocode/service/AccountService.java @@ -2,6 +2,7 @@ import com.bobocode.dao.AccountDao; import com.bobocode.model.Account; +import org.springframework.stereotype.Service; import java.util.Comparator; import java.util.List; @@ -12,6 +13,7 @@ * todo: configure {@link AccountService} bean implicitly using special annotation for service classes * todo: use implicit constructor-based dependency injection (don't use {@link org.springframework.beans.factory.annotation.Autowired}) */ +@Service public class AccountService { private final AccountDao accountDao; From d84533d27521c32d239181a814bf8774154c2df2 Mon Sep 17 00:00:00 2001 From: tboychuk Date: Mon, 24 Sep 2018 15:50:57 +0300 Subject: [PATCH 2/8] Complete "Hello Spring MVC" exercise --- .../src/main/java/com/bobocode/config/RootConfig.java | 5 +++++ .../main/java/com/bobocode/config/WebAppInitializer.java | 7 ++++--- .../src/main/java/com/bobocode/config/WebConfig.java | 7 +++++++ .../com/bobocode/web/controller/WelcomeController.java | 7 +++++++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/hello-spring-mvc/src/main/java/com/bobocode/config/RootConfig.java b/hello-spring-mvc/src/main/java/com/bobocode/config/RootConfig.java index bd4dad0..5782500 100644 --- a/hello-spring-mvc/src/main/java/com/bobocode/config/RootConfig.java +++ b/hello-spring-mvc/src/main/java/com/bobocode/config/RootConfig.java @@ -1,5 +1,8 @@ package com.bobocode.config; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Controller; import org.springframework.web.servlet.config.annotation.EnableWebMvc; @@ -10,5 +13,7 @@ * todo: enable component scanning for all packages in "com.bobocode" * todo: ignore all web related config and beans (ignore @{@link Controller}, ignore {@link EnableWebMvc}) using exclude filter */ +@Configuration +@ComponentScan(basePackages = "com.bobocode", excludeFilters = {@Filter(EnableWebMvc.class), @Filter(Controller.class)}) public class RootConfig { } diff --git a/hello-spring-mvc/src/main/java/com/bobocode/config/WebAppInitializer.java b/hello-spring-mvc/src/main/java/com/bobocode/config/WebAppInitializer.java index 0cec4ca..265a09d 100644 --- a/hello-spring-mvc/src/main/java/com/bobocode/config/WebAppInitializer.java +++ b/hello-spring-mvc/src/main/java/com/bobocode/config/WebAppInitializer.java @@ -12,16 +12,17 @@ public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class[] getRootConfigClasses() { - throw new UnsupportedOperationException("Method is not implemented yet!"); + return new Class[]{RootConfig.class}; } @Override protected Class[] getServletConfigClasses() { - throw new UnsupportedOperationException("Method is not implemented yet!"); + return new Class[]{WebConfig.class}; + } @Override protected String[] getServletMappings() { - throw new UnsupportedOperationException("Method is not implemented yet!"); + return new String[]{"/"}; } } diff --git a/hello-spring-mvc/src/main/java/com/bobocode/config/WebConfig.java b/hello-spring-mvc/src/main/java/com/bobocode/config/WebConfig.java index 975ea15..d94eb7f 100644 --- a/hello-spring-mvc/src/main/java/com/bobocode/config/WebConfig.java +++ b/hello-spring-mvc/src/main/java/com/bobocode/config/WebConfig.java @@ -1,5 +1,9 @@ package com.bobocode.config; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + /** * This class provides web (servlet) related configuration. *

@@ -7,5 +11,8 @@ * todo: enable web mvc using annotation * todo: enable component scanning for package "web" */ +@Configuration +@EnableWebMvc +@ComponentScan(basePackages = "com.bobocode.web") public class WebConfig { } diff --git a/hello-spring-mvc/src/main/java/com/bobocode/web/controller/WelcomeController.java b/hello-spring-mvc/src/main/java/com/bobocode/web/controller/WelcomeController.java index ca389bc..c1209c1 100644 --- a/hello-spring-mvc/src/main/java/com/bobocode/web/controller/WelcomeController.java +++ b/hello-spring-mvc/src/main/java/com/bobocode/web/controller/WelcomeController.java @@ -1,5 +1,9 @@ package com.bobocode.web.controller; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; + /** * Welcome controller that consists of one method that handles get request to "/welcome" and respond with a message. *

@@ -7,8 +11,11 @@ * todo: configure HTTP GET mapping "/welcome" for method {@link WelcomeController#welcome()} * todo: tell Spring that {@link WelcomeController#welcome()} method provides response body */ +@Controller public class WelcomeController { + @ResponseBody + @GetMapping("/welcome") public String welcome() { return "Welcome to Spring MVC!"; } From 8b5399cf5a7dfa613e6a67f28357db386536bd01 Mon Sep 17 00:00:00 2001 From: tboychuk Date: Wed, 26 Sep 2018 14:27:22 +0300 Subject: [PATCH 3/8] Complete the exercise "Account JSP" --- .../config/AccountWebAppInitializer.java | 7 +++--- .../java/com/bobocode/config/RootConfig.java | 11 ++++++++++ .../java/com/bobocode/config/WebConfig.java | 18 +++++++++++++++ .../web/controller/AccountController.java | 22 +++++++++++++++++++ .../web/controller/WelcomeController.java | 10 +++++++++ 5 files changed, 65 insertions(+), 3 deletions(-) diff --git a/account-jsp/src/main/java/com/bobocode/config/AccountWebAppInitializer.java b/account-jsp/src/main/java/com/bobocode/config/AccountWebAppInitializer.java index 3b0ce87..c50ab59 100644 --- a/account-jsp/src/main/java/com/bobocode/config/AccountWebAppInitializer.java +++ b/account-jsp/src/main/java/com/bobocode/config/AccountWebAppInitializer.java @@ -12,16 +12,17 @@ public class AccountWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class[] getRootConfigClasses() { - throw new UnsupportedOperationException("It's your job to implement this method!"); + return new Class[]{RootConfig.class}; } @Override protected Class[] getServletConfigClasses() { - throw new UnsupportedOperationException("It's your job to implement this method!"); + return new Class[]{WebConfig.class}; } @Override protected String[] getServletMappings() { - throw new UnsupportedOperationException("It's your job to implement this method!"); + return new String[]{"/"}; } } + diff --git a/account-jsp/src/main/java/com/bobocode/config/RootConfig.java b/account-jsp/src/main/java/com/bobocode/config/RootConfig.java index 27e98f6..043ea11 100644 --- a/account-jsp/src/main/java/com/bobocode/config/RootConfig.java +++ b/account-jsp/src/main/java/com/bobocode/config/RootConfig.java @@ -1,6 +1,10 @@ package com.bobocode.config; import com.bobocode.TestDataGenerator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Controller; import org.springframework.web.servlet.config.annotation.EnableWebMvc; @@ -12,5 +16,12 @@ * todo: 3. Exclude web related config and beans (ignore @{@link Controller}, ignore {@link EnableWebMvc}) * todo: 4. Configure {@link TestDataGenerator} bean with name "dataGenerator" (don't specify name explicitly) */ +@Configuration +@ComponentScan(basePackages = "com.bobocode", excludeFilters = {@Filter(EnableWebMvc.class), @Filter(Controller.class)}) public class RootConfig { + @Bean + public TestDataGenerator dataGenerator() { + return new TestDataGenerator(); + } } + diff --git a/account-jsp/src/main/java/com/bobocode/config/WebConfig.java b/account-jsp/src/main/java/com/bobocode/config/WebConfig.java index edf8c8a..d40a7cc 100644 --- a/account-jsp/src/main/java/com/bobocode/config/WebConfig.java +++ b/account-jsp/src/main/java/com/bobocode/config/WebConfig.java @@ -1,5 +1,12 @@ package com.bobocode.config; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.ViewResolver; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.view.InternalResourceViewResolver; + /** * This class provides web (servlet) related configuration. *

@@ -8,6 +15,17 @@ * todo: 3. Enable component scanning for package "web" using annotation value * todo: 4. Configure JPS internal view resolver with prefix = "/WEB-INF/views/" and suffix ".jsp" */ +@Configuration +@ComponentScan("com.bobocode.web") +@EnableWebMvc public class WebConfig { + @Bean + public ViewResolver viewResolver() { + InternalResourceViewResolver resolver = new InternalResourceViewResolver(); + resolver.setPrefix("/WEB-INF/views/"); + resolver.setSuffix(".jsp"); + return resolver; + } } + diff --git a/account-jsp/src/main/java/com/bobocode/web/controller/AccountController.java b/account-jsp/src/main/java/com/bobocode/web/controller/AccountController.java index f512655..4e9db47 100644 --- a/account-jsp/src/main/java/com/bobocode/web/controller/AccountController.java +++ b/account-jsp/src/main/java/com/bobocode/web/controller/AccountController.java @@ -2,6 +2,17 @@ import com.bobocode.TestDataGenerator; import com.bobocode.model.Account; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toList; /** * This controller provides endpoint that generates a list of {@link Account} and passes it to the view. @@ -13,6 +24,17 @@ * todo: 5. Provide a default value "10" for parameter "size" * todo: 6. Pass the list of accounts to the view using model attribute with name "accountList" */ +@Controller +@RequestMapping("/accounts") public class AccountController { + @Autowired + private TestDataGenerator dataGenerator; + @GetMapping + public String getAccounts(@RequestParam(name = "size", defaultValue = "10") int size, Model model) { + List accounts = Stream.generate(dataGenerator::generateAccount).limit(size).collect(toList()); + model.addAttribute("accountList", accounts); + return "accounts"; + } } + diff --git a/account-jsp/src/main/java/com/bobocode/web/controller/WelcomeController.java b/account-jsp/src/main/java/com/bobocode/web/controller/WelcomeController.java index b78e0e5..7854a6b 100644 --- a/account-jsp/src/main/java/com/bobocode/web/controller/WelcomeController.java +++ b/account-jsp/src/main/java/com/bobocode/web/controller/WelcomeController.java @@ -1,5 +1,8 @@ package com.bobocode.web.controller; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + /** * Welcome controller that consists of one method that handles get request to "/" and "/welcome" and respond with a message. *

@@ -7,6 +10,13 @@ * todo: 2. Configure HTTP GET mapping "/" and "/welcome" for method {@link WelcomeController#welcome()} * todo: 3. Forward the request to "welcome.jsp" view */ +@Controller public class WelcomeController { + @GetMapping({"/", "/welcome"}) + public String welcome() { + return "welcome"; + } + } + From f49df5bda0b6dbe9512233a6640f8fea083dcd8a Mon Sep 17 00:00:00 2001 From: tboychuk Date: Thu, 27 Sep 2018 17:49:25 +0300 Subject: [PATCH 4/8] Complete the exercise "Account REST API" --- .../java/com/bobocode/config/RootConfig.java | 7 +++ .../java/com/bobocode/config/WebConfig.java | 9 ++++ .../bobocode/dao/impl/InMemoryAccountDao.java | 2 + .../web/controller/AccountRestController.java | 46 +++++++++++++++++++ 4 files changed, 64 insertions(+) diff --git a/account-rest-api/src/main/java/com/bobocode/config/RootConfig.java b/account-rest-api/src/main/java/com/bobocode/config/RootConfig.java index c5d1a6d..fe8f56e 100644 --- a/account-rest-api/src/main/java/com/bobocode/config/RootConfig.java +++ b/account-rest-api/src/main/java/com/bobocode/config/RootConfig.java @@ -1,5 +1,9 @@ package com.bobocode.config; +import com.bobocode.TestDataGenerator; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Controller; import org.springframework.web.servlet.config.annotation.EnableWebMvc; @@ -10,5 +14,8 @@ * todo: 2. Enable component scanning for all packages in "com.bobocode" using annotation property "basePackages" * todo: 3. Exclude web related config and beans (ignore @{@link Controller}, ignore {@link EnableWebMvc}) */ +@Configuration +@ComponentScan(basePackages = "com.bobocode", excludeFilters = {@Filter(EnableWebMvc.class), @Filter(Controller.class)}) public class RootConfig { } + diff --git a/account-rest-api/src/main/java/com/bobocode/config/WebConfig.java b/account-rest-api/src/main/java/com/bobocode/config/WebConfig.java index 6284d6a..2b1b98c 100644 --- a/account-rest-api/src/main/java/com/bobocode/config/WebConfig.java +++ b/account-rest-api/src/main/java/com/bobocode/config/WebConfig.java @@ -1,5 +1,10 @@ package com.bobocode.config; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Controller; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + /** * This class provides web (servlet) related configuration. *

@@ -7,6 +12,10 @@ * todo: 2. Enable web mvc using annotation * todo: 3. Enable component scanning for package "web" using annotation value */ +@EnableWebMvc +@Configuration +@ComponentScan("com.bobocode.web") public class WebConfig { } + diff --git a/account-rest-api/src/main/java/com/bobocode/dao/impl/InMemoryAccountDao.java b/account-rest-api/src/main/java/com/bobocode/dao/impl/InMemoryAccountDao.java index af5f026..0401391 100644 --- a/account-rest-api/src/main/java/com/bobocode/dao/impl/InMemoryAccountDao.java +++ b/account-rest-api/src/main/java/com/bobocode/dao/impl/InMemoryAccountDao.java @@ -3,6 +3,7 @@ import com.bobocode.dao.AccountDao; import com.bobocode.exception.EntityNotFountException; import com.bobocode.model.Account; +import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.HashMap; @@ -14,6 +15,7 @@ *

* todo: 1. Configure a component with name "accountDao" */ +@Component("accountDao") public class InMemoryAccountDao implements AccountDao { private Map accountMap = new HashMap<>(); private long idSequence = 1L; diff --git a/account-rest-api/src/main/java/com/bobocode/web/controller/AccountRestController.java b/account-rest-api/src/main/java/com/bobocode/web/controller/AccountRestController.java index e73a2ee..a30ac66 100644 --- a/account-rest-api/src/main/java/com/bobocode/web/controller/AccountRestController.java +++ b/account-rest-api/src/main/java/com/bobocode/web/controller/AccountRestController.java @@ -1,6 +1,12 @@ package com.bobocode.web.controller; import com.bobocode.dao.AccountDao; +import com.bobocode.model.Account; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Objects; /** *

@@ -16,6 +22,46 @@ * todo: 6. Implement method that handles DELETE request with id as path variable removes an account by id * todo: Configure HTTP response status code 204 - NO CONTENT */ +@RestController +@RequestMapping("/accounts") public class AccountRestController { + private final AccountDao accountDao; + + public AccountRestController(AccountDao accountDao) { + this.accountDao = accountDao; + } + + @GetMapping + public List getAll() { + return accountDao.findAll(); + } + + @GetMapping("/{id}") + public Account getOne(@PathVariable long id) { + return accountDao.findById(id); + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public Account create(@RequestBody Account account) { + return accountDao.save(account); + } + + @PutMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void update(@PathVariable long id, @RequestBody Account account) { + if (!Objects.equals(id, account.getId())) { + throw new IllegalStateException("Id parameter does not match account body value"); + } + accountDao.save(account); + } + + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void remove(@PathVariable long id) { + Account account = accountDao.findById(id); + accountDao.remove(account); + } } + From bdb172ec8bb6d2ec1e7fe4cd82a7beffc72c5d69 Mon Sep 17 00:00:00 2001 From: tboychuk Date: Fri, 28 Sep 2018 17:38:15 +0300 Subject: [PATCH 5/8] Complete the exercise "Transactional User Service" --- .../main/java/com/bobocode/config/JpaConfig.java | 10 ++++++++++ .../main/java/com/bobocode/config/RootConfig.java | 15 +++++++++++++++ .../java/com/bobocode/dao/impl/JpaUserDao.java | 4 ++++ .../java/com/bobocode/service/UserService.java | 12 ++++++++++++ 4 files changed, 41 insertions(+) diff --git a/transactional-user-service/src/main/java/com/bobocode/config/JpaConfig.java b/transactional-user-service/src/main/java/com/bobocode/config/JpaConfig.java index dbb7cc6..de11ce2 100644 --- a/transactional-user-service/src/main/java/com/bobocode/config/JpaConfig.java +++ b/transactional-user-service/src/main/java/com/bobocode/config/JpaConfig.java @@ -1,9 +1,12 @@ package com.bobocode.config; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.orm.jpa.JpaVendorAdapter; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.Database; import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; import javax.sql.DataSource; @@ -21,7 +24,9 @@ * todo: 6. Configure package "com.bobocode.model" to scan for JPA entities * */ +@Configuration public class JpaConfig { + @Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.H2) @@ -29,20 +34,25 @@ public DataSource dataSource() { .build(); } + @Bean public JpaVendorAdapter jpaVendorAdapter() { HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter(); // todo: 4. Set adapter database to tell Hibernate which dialect to use + adapter.setDatabase(Database.H2); adapter.setShowSql(true); adapter.setGenerateDdl(true); // this sets hibernate.hbm2ddl.auto=update (Hibernate will generate db tables) return adapter; } + @Bean("entityManagerFactory") public LocalContainerEntityManagerFactoryBean localContainerEMF(DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) { LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean(); emf.setDataSource(dataSource); emf.setJpaVendorAdapter(jpaVendorAdapter); emf.setPersistenceUnitName("springEntities"); + emf.setPackagesToScan("com.bobocode.model"); // todo: 6. Configure package "com.bobocode.model" to scan for JPA entities return emf; } } + diff --git a/transactional-user-service/src/main/java/com/bobocode/config/RootConfig.java b/transactional-user-service/src/main/java/com/bobocode/config/RootConfig.java index 58d3031..3828e95 100644 --- a/transactional-user-service/src/main/java/com/bobocode/config/RootConfig.java +++ b/transactional-user-service/src/main/java/com/bobocode/config/RootConfig.java @@ -1,6 +1,13 @@ package com.bobocode.config; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import javax.persistence.EntityManagerFactory; /** * This class provides root application configuration. It scans for all available beans and enables declarative @@ -11,6 +18,14 @@ * todo: 3. Configure JPA {@link PlatformTransactionManager} with bean name "transactionManager" * todo: 4. Enable declarative transaction management */ +@Configuration +@ComponentScan("com.bobocode") +@EnableTransactionManagement public class RootConfig { + @Bean + public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { + return new JpaTransactionManager(entityManagerFactory); + } } + diff --git a/transactional-user-service/src/main/java/com/bobocode/dao/impl/JpaUserDao.java b/transactional-user-service/src/main/java/com/bobocode/dao/impl/JpaUserDao.java index e364ed7..99aeb8d 100644 --- a/transactional-user-service/src/main/java/com/bobocode/dao/impl/JpaUserDao.java +++ b/transactional-user-service/src/main/java/com/bobocode/dao/impl/JpaUserDao.java @@ -16,7 +16,10 @@ * todo: 2. Enable transaction management on class level * todo: 3. Inject persistence context into {@link EntityManager} field */ +@Transactional +@Repository("userDao") public class JpaUserDao implements UserDao { + @PersistenceContext private EntityManager entityManager; @Override @@ -34,3 +37,4 @@ public void save(User user) { entityManager.persist(user); } } + diff --git a/transactional-user-service/src/main/java/com/bobocode/service/UserService.java b/transactional-user-service/src/main/java/com/bobocode/service/UserService.java index d6be52c..1e5ed49 100644 --- a/transactional-user-service/src/main/java/com/bobocode/service/UserService.java +++ b/transactional-user-service/src/main/java/com/bobocode/service/UserService.java @@ -4,7 +4,10 @@ import com.bobocode.model.jpa.Role; import com.bobocode.model.jpa.RoleType; import com.bobocode.model.jpa.User; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.math.BigDecimal; import java.util.List; import static java.util.stream.Collectors.toList; @@ -18,17 +21,25 @@ * todo: 4. Configure {@link UserService#getAll()} as read-only method * todo: 4. Configure {@link UserService#getAllAdmins()} as read-only method */ +@Service +@Transactional public class UserService { private UserDao userDao; + public UserService(UserDao userDao) { + this.userDao = userDao; + } + public void save(User user) { userDao.save(user); } + @Transactional(readOnly = true) public List getAll() { return userDao.findAll(); } + @Transactional(readOnly = true) public List getAllAdmins() { return userDao.findAll().stream() .filter(user -> user.getRoles().stream() @@ -43,3 +54,4 @@ public void addRole(Long userId, RoleType roleType) { user.addRole(role); } } + From 580271f48405966a3db9d50887a99e4181c1ffb7 Mon Sep 17 00:00:00 2001 From: tboychuk Date: Sat, 25 May 2019 07:54:57 +0300 Subject: [PATCH 6/8] Fix UserService.java --- .../src/main/java/com/bobocode/config/JpaConfig.java | 2 +- .../src/main/java/com/bobocode/service/UserService.java | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/transactional-user-service/src/main/java/com/bobocode/config/JpaConfig.java b/transactional-user-service/src/main/java/com/bobocode/config/JpaConfig.java index 1f5e19e..dd6f746 100644 --- a/transactional-user-service/src/main/java/com/bobocode/config/JpaConfig.java +++ b/transactional-user-service/src/main/java/com/bobocode/config/JpaConfig.java @@ -45,7 +45,7 @@ public LocalContainerEntityManagerFactoryBean localContainerEMF(DataSource dataS LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean(); emf.setDataSource(dataSource); emf.setJpaVendorAdapter(jpaVendorAdapter); - // todo: 5. Configure package "com.bobocode.model" to scan for JPA entities + emf.setPackagesToScan("com.bobocode.model"); return emf; } } diff --git a/transactional-user-service/src/main/java/com/bobocode/service/UserService.java b/transactional-user-service/src/main/java/com/bobocode/service/UserService.java index aec9787..1e5ed49 100644 --- a/transactional-user-service/src/main/java/com/bobocode/service/UserService.java +++ b/transactional-user-service/src/main/java/com/bobocode/service/UserService.java @@ -41,7 +41,11 @@ public List getAll() { @Transactional(readOnly = true) public List getAllAdmins() { - throw new UnsupportedOperationException("Don't be lazy and implement the method"); + return userDao.findAll().stream() + .filter(user -> user.getRoles().stream() + .map(Role::getRoleType) + .anyMatch(roleType -> roleType.equals(RoleType.ADMIN))) + .collect(toList()); } public void addRole(Long userId, RoleType roleType) { From f81db839731d3190adc6ad9bef25ae2c1e834db2 Mon Sep 17 00:00:00 2001 From: tboychuk Date: Mon, 23 Sep 2019 23:05:01 +0300 Subject: [PATCH 7/8] Implement new inter-bean-dependencies exercise --- inter-bean-dependencies/pom.xml | 21 +++++++ .../java/com/bobocode/config/RootConfig.java | 30 ++++++++++ .../java/com/bobocode/dao/AccountDao.java | 7 +++ .../com/bobocode/dao/impl/FakeAccountDao.java | 20 +++++++ .../com/bobocode/service/AccountService.java | 12 ++++ .../com/bobocode/RootConfigContextTest.java | 34 +++++++++++ .../com/bobocode/RootConfigHacksTest.java | 58 +++++++++++++++++++ pom.xml | 7 +++ 8 files changed, 189 insertions(+) create mode 100644 inter-bean-dependencies/pom.xml create mode 100644 inter-bean-dependencies/src/main/java/com/bobocode/config/RootConfig.java create mode 100644 inter-bean-dependencies/src/main/java/com/bobocode/dao/AccountDao.java create mode 100644 inter-bean-dependencies/src/main/java/com/bobocode/dao/impl/FakeAccountDao.java create mode 100644 inter-bean-dependencies/src/main/java/com/bobocode/service/AccountService.java create mode 100644 inter-bean-dependencies/src/test/java/com/bobocode/RootConfigContextTest.java create mode 100644 inter-bean-dependencies/src/test/java/com/bobocode/RootConfigHacksTest.java diff --git a/inter-bean-dependencies/pom.xml b/inter-bean-dependencies/pom.xml new file mode 100644 index 0000000..b0899a2 --- /dev/null +++ b/inter-bean-dependencies/pom.xml @@ -0,0 +1,21 @@ + + + + spring-framework-exercises + com.bobocode + 1.0-SNAPSHOT + + 4.0.0 + + bean-configuration-exercise + + + + com.bobocode + spring-framework-exercises-util + 1.0-SNAPSHOT + + + \ No newline at end of file diff --git a/inter-bean-dependencies/src/main/java/com/bobocode/config/RootConfig.java b/inter-bean-dependencies/src/main/java/com/bobocode/config/RootConfig.java new file mode 100644 index 0000000..40419b3 --- /dev/null +++ b/inter-bean-dependencies/src/main/java/com/bobocode/config/RootConfig.java @@ -0,0 +1,30 @@ +package com.bobocode.config; + +import com.bobocode.TestDataGenerator; +import com.bobocode.dao.AccountDao; +import com.bobocode.dao.impl.FakeAccountDao; +import com.bobocode.service.AccountService; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +/** + * todo: Refactor {@link RootConfig} in order to user inter-bean dependencies properly. + */ +@Component +public final class RootConfig { + + @Bean + public AccountService accountService() { + return new AccountService(fakeAccountDao()); + } + + @Bean + public final AccountDao fakeAccountDao() { + return new FakeAccountDao(dataGenerator()); + } + + @Bean + private TestDataGenerator dataGenerator() { + return new TestDataGenerator(); + } +} diff --git a/inter-bean-dependencies/src/main/java/com/bobocode/dao/AccountDao.java b/inter-bean-dependencies/src/main/java/com/bobocode/dao/AccountDao.java new file mode 100644 index 0000000..4add548 --- /dev/null +++ b/inter-bean-dependencies/src/main/java/com/bobocode/dao/AccountDao.java @@ -0,0 +1,7 @@ +package com.bobocode.dao; + +import com.bobocode.model.Account; + +public interface AccountDao { + Account findById(Long id); +} diff --git a/inter-bean-dependencies/src/main/java/com/bobocode/dao/impl/FakeAccountDao.java b/inter-bean-dependencies/src/main/java/com/bobocode/dao/impl/FakeAccountDao.java new file mode 100644 index 0000000..5f5da87 --- /dev/null +++ b/inter-bean-dependencies/src/main/java/com/bobocode/dao/impl/FakeAccountDao.java @@ -0,0 +1,20 @@ +package com.bobocode.dao.impl; + +import com.bobocode.TestDataGenerator; +import com.bobocode.dao.AccountDao; +import com.bobocode.model.Account; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class FakeAccountDao implements AccountDao { + private final TestDataGenerator dataGenerator; + + @Override + public Account findById(Long id) { + Account account = dataGenerator.generateAccount(); + account.setId(id); + return account; + } +} diff --git a/inter-bean-dependencies/src/main/java/com/bobocode/service/AccountService.java b/inter-bean-dependencies/src/main/java/com/bobocode/service/AccountService.java new file mode 100644 index 0000000..23bc16d --- /dev/null +++ b/inter-bean-dependencies/src/main/java/com/bobocode/service/AccountService.java @@ -0,0 +1,12 @@ +package com.bobocode.service; + +import com.bobocode.dao.AccountDao; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class AccountService { + private final AccountDao accountDao; + +} diff --git a/inter-bean-dependencies/src/test/java/com/bobocode/RootConfigContextTest.java b/inter-bean-dependencies/src/test/java/com/bobocode/RootConfigContextTest.java new file mode 100644 index 0000000..4860714 --- /dev/null +++ b/inter-bean-dependencies/src/test/java/com/bobocode/RootConfigContextTest.java @@ -0,0 +1,34 @@ +package com.bobocode; + +import com.bobocode.config.RootConfig; +import com.bobocode.dao.impl.FakeAccountDao; +import com.bobocode.service.AccountService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + + +@SpringJUnitConfig(classes = RootConfig.class) +class RootConfigContextTest { + + @Autowired + private AccountService accountService; + + @Autowired + private FakeAccountDao accountDao; + + @Autowired + private TestDataGenerator dataGenerator; + + @Test + void dataGeneratorShouldHaveScopeSingleton() { + assertThat(accountDao.getDataGenerator()).isEqualTo(dataGenerator); + } + + @Test + void accountDaoShouldHaveScopeSingleton() { + assertThat(accountService.getAccountDao()).isEqualTo(accountDao); + } +} diff --git a/inter-bean-dependencies/src/test/java/com/bobocode/RootConfigHacksTest.java b/inter-bean-dependencies/src/test/java/com/bobocode/RootConfigHacksTest.java new file mode 100644 index 0000000..1e7501e --- /dev/null +++ b/inter-bean-dependencies/src/test/java/com/bobocode/RootConfigHacksTest.java @@ -0,0 +1,58 @@ +package com.bobocode; + +import com.bobocode.config.RootConfig; +import com.bobocode.dao.impl.FakeAccountDao; +import com.bobocode.service.AccountService; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Import; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +class RootConfigHacksTest { + + @Test + void rootConfigShouldNotUseComponentScan() { + ComponentScan componentScan = RootConfig.class.getAnnotation(ComponentScan.class); + + assertThat(componentScan).isNull(); + } + + @Test + void rootConfigShouldNotImportOtherConfigs() { + Import importAnnotation = RootConfig.class.getAnnotation(Import.class); + + assertThat(importAnnotation).isNull(); + } + + @Test + void accountServiceBeanShouldBeConfiguredExplicitly() { + Annotation[] annotations = AccountService.class.getAnnotations(); + List annotationClasses = Stream.of(annotations).map(Annotation::annotationType).collect(Collectors.toList()); + + assertThat(annotationClasses).doesNotContain(Component.class, Service.class); + } + + @Test + void fakeAccountDaoBeanShouldBeConfiguredExplicitly() { + Annotation[] annotations = FakeAccountDao.class.getAnnotations(); + List annotationClasses = Stream.of(annotations).map(Annotation::annotationType).collect(Collectors.toList()); + + assertThat(annotationClasses).doesNotContain(Component.class, Service.class); + } + + @Test + void rootConfigShouldUseInterBeanDependencies() { + Method[] methods = RootConfig.class.getDeclaredMethods(); + + assertThat(methods).noneMatch(method -> method.getParameterCount() > 0); + } +} diff --git a/pom.xml b/pom.xml index 4f73839..6609079 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,7 @@ account-jsp account-rest-api transactional-user-service + inter-bean-dependencies pom @@ -78,6 +79,12 @@ slf4j-simple 1.7.24 + + org.assertj + assertj-core + 3.13.2 + test + From d906157d4f6162bcfb5c480c13a62e2f62d0c932 Mon Sep 17 00:00:00 2001 From: tboychuk Date: Mon, 23 Sep 2019 23:16:31 +0300 Subject: [PATCH 8/8] Implement inter-bean dependencies exercise --- .../java/com/bobocode/config/RootConfig.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/inter-bean-dependencies/src/main/java/com/bobocode/config/RootConfig.java b/inter-bean-dependencies/src/main/java/com/bobocode/config/RootConfig.java index 40419b3..6d6bfca 100644 --- a/inter-bean-dependencies/src/main/java/com/bobocode/config/RootConfig.java +++ b/inter-bean-dependencies/src/main/java/com/bobocode/config/RootConfig.java @@ -5,26 +5,32 @@ import com.bobocode.dao.impl.FakeAccountDao; import com.bobocode.service.AccountService; import org.springframework.context.annotation.Bean; -import org.springframework.stereotype.Component; +import org.springframework.context.annotation.Configuration; /** * todo: Refactor {@link RootConfig} in order to user inter-bean dependencies properly. */ -@Component -public final class RootConfig { +@Configuration // @Configuration works a bit differently then @Component +// In this case Spring uses CGLIB to create a proxy in order to support inter-bean dependencies. +// It's when you call other method in your config class and still get the same bean instance +public class RootConfig { // Because CGLIB proxy is created as a subclass of your @Configuration class, RootConfig cannot be final @Bean public AccountService accountService() { - return new AccountService(fakeAccountDao()); + return new AccountService(fakeAccountDao()); // Although you call fakeAccountDao() method here, it is not called directly + // Spring creates proxy that overrides all these method. It caches created beans so when you call method like + // fakeAccountDao() it first checks if such beans was created before. + // It allows to support default bean scope SINGLETON. } @Bean - public final AccountDao fakeAccountDao() { + public AccountDao fakeAccountDao() { return new FakeAccountDao(dataGenerator()); } @Bean - private TestDataGenerator dataGenerator() { + public TestDataGenerator dataGenerator() { // Because Spring overrides methods in a proxy, + // bean methods in @Configuration classes cannot be final return new TestDataGenerator(); } }