don't understand ioc java behavior - spring

I try use IoC without xml. But I don't understand why #Autowired workin in the first case, and doesn't work in second case:
I have 3 classes:
#Configuration
public class DataSourceBean{
#Bean
public DataSource dataSource(){
DataSource ds = new DataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://192.168.1.99:3306/somethink");
ds.setUsername("devusr");
ds.setPassword("root");
ds.setInitialSize(5);
ds.setMaxActive(10);
ds.setMaxIdle(5);
ds.setMinIdle(2);
return ds;
}
}
public class AbstractDao {
#Autowired
private DataSource dataSource;
#Autowired
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public AbstractDao(){
System.out.println("dataSource = " + dataSource);
}
}
and
#RestController
public class PageController {
#Autowired
private DataSource dataSource;
private AbstractDao dao;
#RequestMapping(value = "/test" , method = RequestMethod.GET)
public String homePage(){
// System.out.println("$$ dataSource = " + dataSource);
AbstractDao dao = new AbstractDao();
return "";
}
}
and in a PageControllers autowiring works properly, I see that it doesn't null. And when I create new AbstractDao autowired doesn't work and dataSourse == null . I try add some annotations to class AbstractDao, but it doesn't work. what am I doing wrong? and how I must do it properly? Thanks

In your PageController you have to inject AbstractDao. Autowiring does not work when instantiating Objects with new operator. Try this instead in your PageController:
#RestController
public class PageController {
#Autowired
private DataSource dataSource;
#Autowired
private AbstractDao dao;
#RequestMapping(value = "/test" , method = RequestMethod.GET)
public String homePage(){
// System.out.println("$$ dataSource = " + dataSource);
return "";
}
}

Related

Initialize datasource bean on condition

So, I need to make an initialization of DataSource by condition. I take all the db-config data from the .env file, where there is a special variable spring_profiles_active. When this variable is equal to "p2" I need to initialize secondDataSource, otherwise not. How do I do this?
My configuration:
#Configuration
public class JpaConfig {
#Value("${jdbc.url}")
private String jdbcUrl;
#Value("${jdbc.username}")
private String jdbcUsername;
#Value("${jdbc.password}")
private String jdbcPassword;
#Value("${jdbc.driverClassName}")
private String jdbcDriverClassName;
#Value("${second.jdbc.url}")
private String secondJdbcUrl;
#Value("${second.jdbc.username}")
private String secondJdbcUsername;
#Value("${second.jdbc.password}")
private String secondJdbcPassword;
#Value("${second.jdbc.driverClassName}")
private String secondJdbcDriverClassName;
#Bean
#Primary
public DataSource dataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(jdbcDriverClassName);
dataSource.setUrl(jdbcUrl);
dataSource.setUsername(jdbcUsername);
dataSource.setPassword(EncryptionUtil.decryptProperty(jdbcPassword));
return dataSource;
}
#Bean
#Qualifier("secondDataSource")
public DataSource secondDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(secondJdbcDriverClassName);
dataSource.setUrl(secondJdbcUrl);
dataSource.setUsername(secondJdbcUsername);
dataSource.setPassword(EncryptionUtil.decryptProperty(secondJdbcPassword));
return dataSource;
}
#Bean
#Primary
public JdbcTemplate jdbcTemplate() { return new JdbcTemplate(dataSource()); }
#Bean
#Qualifier("secondJdbcTemplate")
public JdbcTemplate secondJdbcTemplate(#Qualifier("secondDataSource") DataSource secondDataSource) {
return new JdbcTemplate(secondDataSource);
}
}
update:
I try to do this with #Condition annotation, but get an exception java.lang.NullPointerException: null in return activeProfile.equals("p2"). code:
#Configuration
#Conditional(SecondJpaConfigCondition.class)
public class SecondJpaConfig {
#Value("${second.jdbc.url}")
private String secondJdbcUrl;
#Value("${second.jdbc.username}")
private String secondJdbcUsername;
#Value("${second.jdbc.password}")
private String secondJdbcPassword;
#Value("${second.jdbc.driverClassName}")
private String secondJdbcDriverClassName;
#Bean
#Qualifier("secondDataSource")
public DataSource secondDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(secondJdbcDriverClassName);
dataSource.setUrl(secondJdbcUrl);
dataSource.setUsername(secondJdbcUsername);
dataSource.setPassword(EncryptionUtil.decryptProperty(secondJdbcPassword));
return dataSource;
}
#Bean
#Qualifier("secondJdbcTemplate")
public JdbcTemplate secondJdbcTemplate(#Qualifier("secondDataSource") DataSource secondDataSource) {
return new JdbcTemplate(secondDataSource);
}
}
#Component
public class SecondJpaConfigCondition implements Condition {
#Value("${spring.profiles.active}")
private String activeProfile;
#Override
public boolean matches(ConditionContext arg0, AnnotatedTypeMetadata arg1) {
return activeProfile.equals("p2");
}
}
I recommend you to use the #ConditionalOnProperty annotation. It can be used on the class or a method with the #Bean annotation. I would separate the configuration of the two databases in two different classes (it's not mandatory, you could us the annotion over the secondDataSource and secondJdbcTemplate methods) and do something like this:
JpaConfig (First Datasource):
#Configuration
public class JpaConfig {
#Value("${jdbc.url}")
private String jdbcUrl;
#Value("${jdbc.username}")
private String jdbcUsername;
#Value("${jdbc.password}")
private String jdbcPassword;
#Value("${jdbc.driverClassName}")
private String jdbcDriverClassName;
#Bean
#Primary
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(jdbcDriverClassName);
dataSource.setUrl(jdbcUrl);
dataSource.setUsername(jdbcUsername);
dataSource.setPassword(EncryptionUtil.decryptProperty(jdbcPassword));
return dataSource;
}
#Bean
#Primary
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource());
}
}
JpaConditionalConfig (Second Datasource):
#Configuration
#ConditionalOnProperty(prefix = "cond", name = "spring_profiles_active", havingValue = "p2")
public class JpaOptionalConfig {
#Value("${second.jdbc.url}")
private String secondJdbcUrl;
#Value("${second.jdbc.username}")
private String secondJdbcUsername;
#Value("${second.jdbc.password}")
private String secondJdbcPassword;
#Value("${second.jdbc.driverClassName}")
private String secondJdbcDriverClassName;
#Bean
#Qualifier("secondDataSource")
public DataSource secondDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(secondJdbcDriverClassName);
dataSource.setUrl(secondJdbcUrl);
dataSource.setUsername(secondJdbcUsername);
dataSource.setPassword(EncryptionUtil.decryptProperty(secondJdbcPassword));
return dataSource;
}
#Bean
#Qualifier("secondJdbcTemplate")
public JdbcTemplate secondJdbcTemplate(#Qualifier("secondDataSource") DataSource secondDataSource) {
return new JdbcTemplate(secondDataSource);
}
}
Properties file where is defined the spring_profiles_active property, which will be read by Spring to know if the conditional beans should be created:
jdbc.url=jdbc:h2:mem:mydb
jdbc.username=sa
jdbc.password=password
jdbc.driverClassName=org.h2.Driver
second.jdbc.url=jdbc:h2:mem:mydb
second.jdbc.username=sa
second.jdbc.password=password
second.jdbc.driverClassName=org.h2.Driver
#THIS IS THE PROPERTY USED IN THE CONDITIONAL BEANS
conditional.spring_profiles_active=p2

How to set up an automated integration test to check an aspect functionality with Spring-Boot

I've added an AOP (Aspect Oriented Programming) Aspect to my working project. It does work, but it won't be called when trying to Test it's functionality with an Integration Test.
The problem is, that the aspect is not called when the tests runs through. When using it normally it works fine.
I've tried to create a custom context which is supposed to be loaded for the integration tests, as I thought that the Aspect might not be loaded in the default context for these tests.
As this didn't work i also tried to manually proxy the bean of the aspect, but this didn't work neither.
Here's my integration test class:
#ComponentScan(basePackages = { "package.aspects" })
#RunWith(SpringRunner.class)
#SpringBootTest(classes = ZirafyApp.class)
#ContextConfiguration(classes ={ IntegrationTestAOPConfiguration.class })
public class CellResourceIntTest {
private static CellTestHelper helper = new CellTestHelper();
#Autowired
private PageableHandlerMethodArgumentResolver pageableHandlerMethodArgumentResolver;
#Autowired
private ExceptionTranslator exceptionTranslator;
#Autowired
private EntityManager em;
#Autowired
private BusinessFacade businessFacade;
#Autowired
private CellRepository cellRepository;
#Autowired
private AspectModule aspectModule;
private MockMvc restCellMockMvc;
private MappingJackson2HttpMessageConverter jacksonMessageConverter = new MappingJackson2HttpMessageConverter();
private Cell cell;
private Cell parentCell;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
final CellResource cellResource = new CellResource(cellRepository, businessFacade);
this.restCellMockMvc = MockMvcBuilders.standaloneSetup(cellResource)
.setCustomArgumentResolvers(pageableHandlerMethodArgumentResolver)
.setControllerAdvice(exceptionTranslator)
.setConversionService(createFormattingConversionService())
.setMessageConverters(jacksonMessageConverter).build();
}
#Test
#Transactional
public void update_cellDtoWithEmptyName_returnsHttpError422AndCellInDbIsNotUpdated() throws Exception {
AspectJProxyFactory factory = new AspectJProxyFactory(cellRepository);
factory.addAspect(aspectModule);
CellRepository cellRepository = factory.getProxy();
CellDto cellDtoToUpdate = new CellDto.Builder().id(2).name(null).x(-10).active(true).parent(1).build();
Cell parentCell = helper.createCell(1L);
Cell cellToUpdate = helper.createCell(2L);
cellRepository.saveAndFlush(parentCell);
cellRepository.saveAndFlush(cellToUpdate);
restCellMockMvc.perform(put("/api/cells/update")
.contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(cellDtoToUpdate)))
.andExpect(status().is(200));
Cell updatedCell = cellRepository.findOne(2L);
assertEquals(cellToUpdate.getX(), updatedCell.getX());
}
Here the configuration file for the integration test:
#Configuration
#EnableJpaRepositories(basePackages = {"package.repository"})
#ComponentScan("ch.post.pf.aspects")
#EnableAspectJAutoProxy(proxyTargetClass = true)
public class IntegrationTestAOPConfiguration {
#Autowired
private ExceptionTranslator exceptionTranslator;
#Autowired
private EntityManager em;
#Autowired
private CellConverter cellConverter;
#Autowired
private CellTreeService cellTreeService;
#Autowired
private CellService cellService;
#Autowired
private CellRepository cellRepository;
#Autowired
private BusinessFacade businessFacade;
#Autowired
private AspectModule aspectModule;
#Bean
public CellConverter returnCellConverter() {
return cellConverter;
}
#Bean
public AspectModule returnAspectModule() {
return null;//Aspects.aspectOf(AspectModule.class);
}
#Bean
public PageableHandlerMethodArgumentResolver returnPageableArgumentResolver() {
return new PageableHandlerMethodArgumentResolver();
}
#Bean
public ExceptionTranslator returnExceptionTranslator() {
return exceptionTranslator;
}
#Bean
#Primary
public EntityManager returnEntityManager() { return em; }
#Bean
public BusinessFacade returnBusinessFacade() {
return businessFacade;
}
#Bean
public CellTreeService returnCellTreeService() {
return cellTreeService;
}
#Bean
public CellService returnCellService() {
return cellService;
}
}
And here my aspect-file:
#Aspect
#Component
public class AspectModule {
private BusinessFacade businessFacade;
#Autowired
AspectModule(BusinessFacade businessFacade){
this.businessFacade = businessFacade;
}
#Pointcut("execution(* ch.post.pf.web.rest.CellResource.update(..))")
private void update() {}
#Around("update() && args(cell)")
public Object checkIsValidCell(ProceedingJoinPoint pjp, CellDto cell) {
System.out.println("Aspect was run");
final String message = canUpdate(cell);
if (message.equals("cell_valid")) {
try {
return pjp.proceed(); // Calls the usual update() function, if the cell is valid
} catch (Throwable e) {
System.out.println("Something went wrong with the aspects");
System.out.println(e.toString());
return null;
}
} else {
deleteIfCellWasEmpty(cell);
return ResponseUtil.unprocessableEntity(message);
}
}
}
The aspect should keep working as it does right now but it should also work in the integration tests, at the moment it isn't called at all inside those.

Spring JDBCTemplate. Null pointer exception

I'm trying to set up a SpringMVC website from scratch, but I've hit a dead end.
I'm using autowiring to instanciate JdbcTemplate with a DataSource, but somehow I'm getting a Null pointer exception. I'd appreciate your help with this.
My AppConfig is the next:
#Configuration
#ComponentScan
public class AppConfig {
#Bean
public DriverManagerDataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/onlinelibrary");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
#Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
/*Deleted this code, still doesn't work
#Bean
public Book Book() {
return new Book();
}
*/
}
My Book class is as follows:
#Component
public class Book {
private JdbcTemplate jdbcTemplate;
private String title;
private String author;
private String isbn;
public Book() {
}
#Autowired
public Book(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public ModelMap getBooks() {
ModelMap model = new ModelMap();
String sql = "SELECT * FROM Books";
model.put("data", jdbcTemplate.queryForList(sql));
return model;
}
}
And this is the infamous NullPointer Exception:
Any help would be highly appreciated. I probably forgot to do something, but I can't solve it myself, and I can't find anything on StackOverflow that helps me, either (although I've read many articles by now).
UPDATE WITH MORE DATA:
My project structure is the next:
And I'm using the Book object in this controller:
#Controller
public class BookController {
#RequestMapping(value = "/", method = RequestMethod.GET)
public String getBookData(Book book, ModelMap model) {
model.put("data", book.getBooks());
return "BookView";
}
}
When you have #Component over the class, it means Spring will create a Bean for you provided your component scanner is scanning Book class. You don't need
#Bean
public Book Book() {
return new Book();
}
It's because of this bean that doesn't have jdbcTemplate injected which is throwing NullPointerException.
Update:
Your understanding about spring injection is wrong. I have updated the controller code that should work.
#Controller
public class BookController {
#Autowired
Book book;
#RequestMapping(value = "/", method = RequestMethod.GET)
public String getBookData(ModelMap model) {
model.put("data", book.getBooks());
return "BookView";
}
}
Update: Component Scan
#ComponentScan(basePackages = "models")
public class AppConfig {

Spring #Transactional rollback not working

#RestController
#RequestMapping("/Api/Order")
public class OrderController {
private OrderService service;
private RefundService refundService;
#AsCustomer
#DeleteMapping(value = "/{orderID}/RefundApplication")
#Transactional(rollbackFor = RuntimeException.class)
public Map cancelRefundApplication(#SessionAttribute("user") User user,
#PathVariable("orderID") String orderID) {
Order order = service.getOrderByID(orderID);
RefundApplication application = refundService.get(orderID);
order.setState(Order.STATE_PAYED);
refundService.delete(orderID);
service.updateOrder(order);
throw new EntityNotFoundException("test");
}
...
I want transaction created in cancelRefundApplication method to be rolled back when a RuntimeException is thrown, and to be commit if no RuntimeException is thrown. But I find the transaction is not rolled back even if a RuntimeException is thrown. For test perpose, I change the code to make it always throw a EntityNotFoundException, and test it with following test method. After running the test, I check database and find refund application data is deleted, which means transaction is not rolled back and #Transactional annotation is not working.
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = {WebConfig.class, RootConfig.class, DataConfig.class})
#WebAppConfiguration
class OrderControllerTest {
#Autowired
OrderController controller;
#Autowired
UserService userService;
#Autowired
OrderService orderService;
#Autowired
AppWideExceptionHandler exceptionHandler;
private User customer;
private User seller;
private HashMap<String, Object> sessionAttrs;
private ResultMatcher success = jsonPath("$.code")
.value("0");
private MockMvc mockMvc;
#Test
void cancelRefundApplication() throws Exception {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
String path = String.format("/Api/Order/%s%d0001/RefundApplication"
, simpleDateFormat.format(new Date()), customer.getID());
mockMvc.perform(delete(path)
.characterEncoding("UTF-8")
.sessionAttrs(sessionAttrs))
.andDo(print())
.andExpect(success);
}
...
This is DataConfig class:
#Configuration
#MapperScan("youshu.mapper")
public class DataConfig {
#Bean
public DataSource dataSource() {
// org.apache.ibatis.logging.LogFactory.useLog4J2Logging();
PooledDataSource pds = new PooledDataSource();
pds.setDriver("com.mysql.cj.jdbc.Driver");
pds.setUsername(...);
pds.setPassword(...);
pds.setUrl("jdbc:mysql://XXXX");
return pds;
}
#Bean
public JdbcOperations jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
#Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setTypeAliasesPackage("youshu.entity");
return sessionFactory.getObject();
}
#Bean
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
#Bean
public SqlSessionTemplate sqlSession(SqlSessionFactory factory){
return new SqlSessionTemplate(factory);
}
}
Transactions need to be enabled manually by annotating config class with #EnableTransactionManagement
Check include or not TransactionalTestExecutionListener in your test, if not add: #TestExecutionListeners(listeners = {TransactionalTestExecutionListener.class})

Hibernate Transaction Advice in Spring MVC

My project is using Spring MVC4, Hibernate 5. I have configured hibernate transaction with Advice Interceptor, but it does not rollback as I would like. Please help me, what is the problem with my configuration?
All my code is as below:
1. Hibernate config:
#Configuration
#EnableTransactionManagement
public class DataSourceConfiguration {
#Autowired
private Environment env;
#Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan(
new String[] {env.getProperty("spring.hibernate.packagesToScan")});
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("spring.datasource.driver-class-name"));
dataSource.setUrl(env.getProperty("spring.datasource.url"));
dataSource.setUsername(env.getProperty("spring.datasource.username"));
dataSource.setPassword(env.getProperty("spring.datasource.password"));
return dataSource;
}
#Bean
#Autowired
public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(sessionFactory);
return txManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
}
Spring Advice Interceptor:
#Aspect
#Configuration
public class TxAdviceInterceptor {
private static final String TX_METHOD_NAME = "*";
#Value(value = "${tx-advice.timeout:-1}")
private Integer txMethodTimeout = -1;
private static final String AOP_POINTCUT_EXPRESSION =
"execution(* com.ptg.service..*.*(..))";
#Autowired
private PlatformTransactionManager transactionManager;
#Bean
public TransactionInterceptor txAdvice() {
MatchAlwaysTransactionAttributeSource source = new MatchAlwaysTransactionAttributeSource();
RuleBasedTransactionAttribute transactionAttribute = new RuleBasedTransactionAttribute();
transactionAttribute.setName(TX_METHOD_NAME);
transactionAttribute.setRollbackRules(
Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
transactionAttribute.setTimeout(txMethodTimeout);
source.setTransactionAttribute(transactionAttribute);
return new TransactionInterceptor(transactionManager, source);
}
#Bean
public Advisor txAdviceAdvisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
return new DefaultPointcutAdvisor(pointcut, txAdvice());
}
}
DAO:
#Repository
public abstract class GenericDaoImpl{
#Autowired
private SessionFactory sessionFactory;
#Override
public void S save(S entity) {
sessionFactory.save(entity);
}
}
DaoImpl:
#Repository
public class TagDaoImpl extends GenericDaoImpl{
}
#Repository
public class PostDaoImpl extends GenericDaoImpl{
}
Service:
#Service
public class PostServiceImpl{
#Autowired
private PostDao postDao;
#Autowired
private TagDao tagDao;
public void merge(Post post){
tagDao.save();
postDao.save();
}
}
As code above, I would like if postDao.save is error, tagDao is also rollback.
I have found the problem. My configuration is not wrong.
The problem is "Only unchecked exceptions (that is, subclasses of java.lang.RuntimeException) are rollbacked by default. For the case, a checked exception is thrown, the transaction will be committed".
I have test my code with NullPointerException error, therefore Transaction is not rollbacked.
Refer: https://www.catalysts.cc/wissenswertes/spring-transactional-rollback-on-checked-exceptions/
have you tried #Transactional annotation?

Resources