I know there are a lot of question on SO about this already, but most of them refer to XML configurations, I'm using annotations. My IDE is IntelliJ and I'm using Gradle as my build system.
So I have a really simple web service, I want to enable login and register functionality. My DAO looks like this:
public class UserDAO implements IUserDAO {
#Autowired
private HibernateTemplate hibernateTemplate;
#Override
public void addUser(User user) {
hibernateTemplate.save(user);
}
#Override
public User findByUsername(String username) {
return hibernateTemplate.execute(session -> (User)
session.createCriteria(User.class)
.add(Restrictions.eq("username", username))
.uniqueResult());
}
}
And my config is this:
#Configuration
#EnableTransactionManagement
public class AppConfig {
#Bean
public IUserDAO userDAO() {
return new UserDAO();
}
#Bean
public HibernateTemplate hibernateTemplate() {
return new HibernateTemplate(sessionFactory());
}
#Bean
public SessionFactory sessionFactory() {
return new LocalSessionFactoryBuilder(getDataSource())
.addAnnotatedClasses(User.class)
.buildSessionFactory();
}
#Bean
public DataSource getDataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://example.com:3306/myDatabase");
dataSource.setUsername("myUser");
dataSource.setPassword("myUser123");
return dataSource;
}
#Bean
public HibernateTransactionManager hibTransMan() {
return new HibernateTransactionManager(sessionFactory());
}
}
I use this in my RestController to get the DAO:
#Autowired
private IUserDAO userDAO;
Executing findByUsername works ok and returns the User that matches the given username. However, the addUser method doesnt work, and gives me this error:
Write operations are not allowed in read-only mode (FlushMode.MANUAL): Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition.
I've looked around and found that I should add #Transactional(readOnly = false) to my function, so I did that (I added it above my addUser function, if that's even the right place to put it...), but then I get this error:
No qualifying bean of type [org.springframework.transaction.PlatformTransactionManager] is defined: expected single matching bean but found 2: hibTransMan,transactionManager
Can anyone help me with this situation? Also, for any IntelliJ users, the #Autowired annotation in my UserDAO is flagged with a warning that says Autowired members must be defined in the valid spring bean(#Component/#Service,etc.). Anyone know why this is happening?
P.S. If anybody is startled by the Lambda expression in my findByUsername method, I'm using Java 8.
I found the solution, I added the #Transactional(readOnly = false) annotation to my addUser method, and #Primary to my hibTransMan method in the AppConfig class. However, the warning about #Autowired is still there, anyone know why?
Related
I am connecting to multiple datasources but sometimes some datasources may be offline and at that time I am geting errors on app and application is failing at startup.
I want to skip datasource configuration at startup... I have tried several ways by adding
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
to the application.properties and also I have tried adding
#SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
to the main class but still it tries to configure the datasource.
I also tried to use #Lazy annotation on all methods and on constructor as below but still getting error while creating fooEntityManagerFactory
#Lazy
#Configuration
#EnableJpaRepositories(basePackages = "com.heyo.tayo.repository.foo", entityManagerFactoryRef = "fooEntityManagerFactory", transactionManagerRef = "fooTransactionManager")
public class PersistencefooConfiguration {
#Autowired
private DbContextHolder dbContextHolder;
#Lazy
#Bean
#ConfigurationProperties("tay.datasource.foo")
public DataSourceProperties fooDataSourceProperties() {
return new DataSourceProperties();
}
#Lazy
#Bean
#ConfigurationProperties("tay.datasource.foo.configuration")
public DataSource fooDataSource() {
DataSource dataSource = fooDataSourceProperties().initializeDataSourceBuilder()
.type(BasicDataSource.class).build();
dbContextHolder.addNewAvailableDbType(DbTypeEnum.foo);
return dataSource;
}
#Lazy
#Bean(name = "fooEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean fooEntityManagerFactory(
EntityManagerFactoryBuilder builder) {
//THE CODE IS FAILING AT BELOW RETURN CASE
return builder
.dataSource(fooDataSource())
.packages("com.heyo.tayo.model.foo")
.build();
}
#Lazy
#Bean
public PlatformTransactionManager fooTransactionManager(
final #Qualifier("fooEntityManagerFactory") LocalContainerEntityManagerFactoryBean fooEntityManagerFactory) {
return new JpaTransactionManager(fooEntityManagerFactory.getObject());
}
}
I have multiple classes like above for different configs for different datasources and I am adding them to available dbs static list at datasource Bean.
Here is my dbadapter factory class.
Here is my dbAdaptor factory that creates corresponding db adaptor
#Service
public class DbAdapterFactory {
#Autowired
private BeanFactory beanFactory;
#Autowired
private DbContextHolder dbContextHolder;
public DBAdapter dbAdapter(){
DbTypeEnum currentDb = dbContextHolder.getCurrentDb();
DBAdapter dbAdapter = null;
if(currentDb == DbTypeEnum.FOODB) {
dbAdapter = beanFactory.getBean(foodbadaptor.class);
} else {
dbAdapter = beanFactory.getBean(koodbadaptor.class);
}
return dbAdapter;
}
Here is db context holder that makes operation like setting default db or getting current db etc.:
#Component
public class DbContextHolder {
private DbTypeEnum dbType = DbTypeEnum.FOODB;
private Set<DbTypeEnum> availableDbTypes = new HashSet<>();
public void setCurrentDb(DbTypeEnum dbType) {
this.dbType = dbType;
}
public DbTypeEnum getCurrentDb() {
return this.dbType;
}
public List<DbTypeEnum> getAvailableDbTypes() {
return new ArrayList<>(availableDbTypes);
}
public void addNewAvailableDbType(DbTypeEnum dbTypeEnum) {
availableDbTypes.add(dbTypeEnum);
}
}
I made all #Lazy or tried #SpringBootApplication(exclude={DataSourceAutoConfiguration.class}) but still something is calling to create bean and getting error and app is closing. I want to use that config and datasource in a try-catch block and don't stop application at runtime. How can I achieve this or what am I missing on that configs or annotations ?
I believe that you can simply add in your application properties
spring.sql.init.continue-on-error=true
According to the Spring Boot 2.5.5 user guide:
https://docs.spring.io/spring-boot/docs/2.5.5/reference/htmlsingle/#howto-initialize-a-database-using-spring-jdbc
Spring Boot enables the fail-fast feature of its script-based database initializer. If the scripts cause exceptions, the application fails to start. You can tune that behavior by setting spring.sql.init.continue-on-error.
Depending on your spring boot version the property will be named either
spring.sql.init.continue-on-error
or before Spring Boot 2.5
spring.datasource.continue-on-error
It is so dumb but I solved the problem by adding following to application.properties.
spring.jpa.database=sql_server
I have no idea why I need to specify that explicitly in properties file but the problem is solved. I will search for it
Is it ok to access a datasource in a Spring DAO in the following way (rather than configuring a bean and autowiring it):
#Repository
public class MyDAOImpl extends JdbcDaoSupport implements MyDAO {
#PostConstruct
private void initialize() {
JndiDataSourceLookup lookup = new JndiDataSourceLookup();
lookup.setResourceRef(true);
DataSource dataSource = lookup.getDataSource("jdbc/TWO");
super.setDataSource(dataSource);
}
public MyStuff getMyStuff(Long id) {
// getJdbcTemplate().query() etc
}
}
This works but is it ok or will this cause problems? Maybe create a connection leak or something else?
Thanks
(Edited for clarity)
I want to do some DB related actions in service method. Initialy it looks like this:
#Override
#Transactional
public void addDirectory(Directory directory) {
//some cheks here
directoryRepo.save(directory);
rsdhUtilsService.createPhysTable(directory);
}
Firs method directoryRepo.save(directory); is just simple JPA save action, second one rsdhUtilsService.createPhysTable(directory); is JDBCTemplate stored procedure call from it's own service. The problem is: if any exceptions accures within JPA or SimpleJdbcCall action, transaction will rollback and nothig related to JPA won't be persited, but if exception occures only within JPA action, result of SimpleJdbcCall won't be affected by transaction rollback.
To illustrate this behaviour I've remove JAP action, mark #Transactional as (readOnly = true) and moved all JDBCTemplate related logic from another service to current one.
#Service
public class DirectoriesServiceImpl implements DirectoriesService {
private final DirectoryRepo directoryRepo;
private final MapSQLParamUtils sqlParamUtils;
private final JdbcTemplate jdbcTemplate;
#Autowired
public DirectoriesServiceImpl(DirectoryRepo directoryRepo, MapSQLParamUtils sqlParamUtils, JdbcTemplate jdbcTemplate) {
this.directoryRepo = directoryRepo;
this.sqlParamUtils = sqlParamUtils;
this.jdbcTemplate = jdbcTemplate;
}
#Override
#Transactional(readOnly = true)
public void addDirectory(Directory directory) {
directoryRepo.save(directory);
new SimpleJdbcCall(jdbcTemplate).withSchemaName("RSDH_DICT").withCatalogName("UTL_DICT")
.withFunctionName("create_dict")
.executeFunction(String.class, sqlParamUtils.getMapSqlParamForCreatePhysTable(directory));
}
}
As a result #Transactional annotation is ignored and I can see new records persisted in DB.
I've got only one DataSource configured via application.properties, and here is how JDBCTemlate configured
#Component
class MapSQLParamUtils {
private final DataSource dataSource;
#Autowired
MapSQLParamUtils(DataSource dataSource) {
this.dataSource = dataSource;
}
#Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource);
}
}
So my questions are: why do #Transactional ignored by SimpleJdbcCall and how to configure JPA and JDBCTemlate to use same transaction manager.
UPDATE:
This is how I use this service in controller
#RestController
#RequestMapping(value = "/api/v1/directories")
public class DirectoriesRESTControllerV1 {
private final DirectoriesService directoriesService;
#Autowired
public DirectoriesRESTControllerV1(DirectoriesService directoriesService) {
this.directoriesService = directoriesService;
}
#PostMapping
#PreAuthorize("hasPermission('DIRECTORIES_USER', 'W')")
public ResponseEntity createDirectory(#NotNull #RequestBody DirectoryRequestDTO createDirectoryRequestDTO) {
Directory directoryFromRequest = ServiceUtils.convertDtoToEntity(createDirectoryRequestDTO);
directoriesService.addDirectory(directoryFromRequest);
return ResponseEntity.noContent().build();
}
}
As mentioned earlier, the problem here is that JPA does not execute sql queries at once repository methods called. To enforce it you can use explicit entityManager.flush():
#Autowired
private javax.persistence.EntityManager entityManager;
...
#Override
#Transactional(readOnly = true)
public void addDirectory(Directory directory) {
directoryRepo.save(directory);
entityManager.flush();
new SimpleJdbcCall(jdbcTemplate).withSchemaName("RSDH_DICT").withCatalogName("UTL_DICT")
.withFunctionName("create_dict")
.executeFunction(String.class, sqlParamUtils.getMapSqlParamForCreatePhysTable(directory));
}
To see real SQL queries by hibernate you can enable option show_sql, in case if your application is spring-boot, this configuration enables it:
spring.jpa:
show-sql: true
properties:
hibernate:
format_sql: true
logging.level:
org.hibernate.SQL: DEBUG
Regarding transaction manager. In case if entityManager flush is not enough, you may need the composite transaction manager, that handles both JPA and DataSource. Spring data commons has ChainedTransactionManager. Note: you should be careful with it. I used it this way in my project:
#Bean(BEAN_CONTROLLER_TX)
public PlatformTransactionManager controllerTransactionManager(EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
#Bean(BEAN_ANALYTICS_TX)
public PlatformTransactionManager analyticsTransactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
/**
* Chained both 2 transaction managers.
*
* #return chained transaction manager for controller datasource and analytics datasource
*/
#Primary
#Bean
public PlatformTransactionManager transactionManager(
#Qualifier(BEAN_CONTROLLER_TX) PlatformTransactionManager controllerTransactionManager,
#Qualifier(BEAN_ANALYTICS_TX) PlatformTransactionManager analyticsTransactionManager) {
return new ChainedTransactionManager(controllerTransactionManager, analyticsTransactionManager);
}
Please try this :
#Transactional(rollbackFor = Exception.class)
public void addDirectory(Directory directory){
#Transactional only rolls back transactions for unchecked exceptions. For checked exceptions and their subclasses, it commits data. So although an exception is raised here, because it's a checked exception, Spring ignores it and commits the data to the database.
So if you throw an Exception or a subclass of it, always use the above with the #Transactional annotation to tell Spring to roll back transactions if a checked exception occurs.
It's very simple, just use the following with #Transactional:
#Transactional(rollbackFor = Exception.class)
I've got a strange problem (or perhaps just some misconception about Spring 4.1.7):
There's a component, let's call it MyComponent that looks like this...
#Component
public class MyComponent extends BaseComponent {
...
}
BaseComponent is something like this...
public class BaseComponent {
#Autowired
private HibernateTemplate hibernateTemplate;
public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
this.hibernateTemplate = hibernateTemplate;
}
}
While the class is annotated with #Component for historic reasons, component-scanning is not activated for this package, so the Component will not be found automatically. Thus I have a definition in my #Configuration...
#Configuration
public class MyConfiguration {
#Bean
public HibernateTemplate hibernateTemplate() { ... create one ... }
#Bean
public HibernateTemplate hibernateTemplateSecondary() { ... create another one ... }
#Bean
public MyComponent myComponent() {
MyComponent component = new MyComponent();
component.setHibernateTemplate( hibernateTemplateSecondary() );
return component;
}
}
Unfortunately, what now happens is, that myComponent will be initialized first correctly with the secondary HibernateTemplate, but afterwards, Spring injects the "normal" hibernateTemplate into it (which is wrong).
I've tried to explicitly add autowire = Autowire.NO to the #Bean definition, even if that's already the default... Also there aren't two myComponent beans, it's always the same one...
Has anyone an idea why this happens and if so, how to prevent it? I always thought that when #Bean is involved, #Autowired will not overwrite that?
Please note: Because MyComponent is used in other projects as well, I absolutely cannot touch it. I cannot remove the #Autowired and I cannot add a #Qualifier or similar, because that would break a lot of other projects. Normally, it works fine, since it used the "primary" (and normally, only) database connection. Just in this module, another database should be the primary one and thus MyComponents needs to be told to use the secondary one.
Can't you just use #Qualifier("hibernateTemplateSecondary") in the BaseComponent so it can pick up the right bean?
#Autowired
#Qualifier("hibernateTemplateSecondary")
private HibernateTemplate hibernateTemplate;
Another approach is to use primary annotation which doesn't look good in your case (taking into account that it's your secondary bean)
#Bean
#Primary
public HibernateTemplate hibernateTemplateSecondary() { ... create another one ... }
maybe there's a better solution, but you could solve this with a BeanPostProcessor like this:
#Component
public class MyBeanPostProcessor implements BeanPostProcessor {
#Autowired
#Qualifier("hibernateTemplateSecondary")
private HibernateTemplate hibernateTemplateSecondary;
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof MyComponent) {
((MyComponent) bean).setHibernateTemplate(hibernateTemplateSecondary);
}
return bean;
}
}
This will inject the correct template after the bean has been initialized and will overwrite the wrong one.
I just stumbled on your question by googling on this same issue. I solved it this way:
(tested in spring boot 2.0/spring 5.0)
#Bean
public HibernateTemplate hibernateTemplate() { ... create one ... }
#Bean
public HibernateTemplate hibernateTemplateSecondary() { ... create another one ... }
#Bean
public MyComponent myComponent() {
// Let Spring do its job
return new MyComponent();
}
#Bean
public Void tuneMyComponent(MyComponent myComponent,
#Qualifier("hibernateTemplateSecondary") HibernateTemplate tpl) {
// Override whatever you want
myComponent.setHibernateTemplate(tpl);
// Not really a Bean
return null;
}
I've been using Spring with MyBatis and it's been working really well for a single database. I ran into difficulties when trying to add another database (see reproducible example on Github).
I'm using Spring Java configuration (i.e. not XML). Most of the examples I've seen show how to achieve this using XML.
I have two data configuration classes (A & B) like this:
#Configuration
#MapperScan("io.woolford.database.mapper")
public class DataConfigDatabaseA {
#Bean(name="dataSourceA")
public DataSource dataSourceA() throws SQLException {
SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
dataSource.setDriver(new com.mysql.jdbc.Driver());
dataSource.setUrl("jdbc:mysql://" + dbHostA + "/" + dbDatabaseA);
dataSource.setUsername(dbUserA);
dataSource.setPassword(dbPasswordA);
return dataSource;
}
#Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSourceA());
return sessionFactory.getObject();
}
}
Two mappers, and a service that autowires the mappers:
#Service
public class DbService {
#Autowired
private DbMapperA dbMapperA;
#Autowired
private DbMapperB dbMapperB;
public List<Record> getDabaseARecords(){
return dbMapperA.getDatabaseARecords();
}
public List<Record> getDabaseBRecords(){
return dbMapperB.getDatabaseBRecords();
}
}
The application won't start:
Error creating bean with name 'dataSourceInitializer':
Invocation of init method failed; nested exception is
org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type [javax.sql.DataSource] is defined:
expected single matching bean but found 2: dataSourceB,dataSourceA
I've read that it's possible to use the #Qualifier annotation to disambiguate the autowiring, though I wasn't sure where to add it.
Can you see where I'm going wrong?
If you want to use two data sources at same time and they are not primary and secondary, you should disable DataSourceAutoConfiguration by #EnableAutoConfiguration(excludes = {DataSourceAutoConfiguration.class}) on your application annotated by #SpringBootApplication. Afterwards, you can create your own SqlSessionFactory and bundle your own DataSource. If you also want to use DataSourceTransactionManager, you should do that too.
In this case, you haven't disabled DataSourceAutoConfiguration, so spring framework will try to #Autowired only one DataSource but got two, error occurs.
As what I've said before, you should disable DataSourceAutoConfiguration and configure it manually.
You can disable data source auto configuration as following:
#SpringBootApplication
#EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
public class YourApplication implements CommandLineRunner {
public static void main (String... args) {
SpringApplication.run(YourApplication.class, args);
}
}
And if you are really want to use multiple databases at same time, I suggest you to registering proper bean manually, such as:
package xyz.cloorc.boot.mybatis;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Repository;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.sql.DataSource;
#Configuration
public class SimpleTest {
private DataSource dsA;
private DataSource dsB;
#Bean(name = "dataSourceA")
public DataSource getDataSourceA() {
return dsA != null ? dsA : (dsA = new BasicDataSource());
}
#Bean(name = "dataSourceB")
public DataSource getDataSourceB() {
return dsB != null ? dsB : (dsB = new BasicDataSource());
}
#Bean(name = "sqlSessionFactoryA")
public SqlSessionFactory getSqlSessionFactoryA() throws Exception {
// set DataSource to dsA
return new SqlSessionFactoryBean().getObject();
}
#Bean(name = "sqlSessionFactoryB")
public SqlSessionFactory getSqlSessionFactoryB() throws Exception {
// set DataSource to dsB
return new SqlSessionFactoryBean().getObject();
}
}
#Repository
public class SimpleDao extends SqlSessionDaoSupport {
#Resource(name = "sqlSessionFactoryA")
SqlSessionFactory factory;
#PostConstruct
public void init() {
setSqlSessionFactory(factory);
}
#Override
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
super.setSqlSessionFactory(sqlSessionFactory);
}
public <T> T get (Object id) {
return super.getSqlSession().selectOne("sql statement", "sql parameters");
}
}
In the end, we put each mapper in its own folder:
src/main/java/io/woolford/database/mapper/a/DbMapperA.java
src/main/java/io/woolford/database/mapper/c/DbMapperB.java
We then created two DataConfig classes, one for each database. The #MapperScan annotation resolved the expected single matching bean but found 2 issue.
#Configuration
#MapperScan(value = {"io.woolford.database.mapper.a"}, sqlSessionFactoryRef="sqlSessionFactoryA")
public class DataConfigDatabaseA {
It was necessary to add the #Primary annotation to the beans in one of the DataConfig classes:
#Bean(name="dataSourceA")
#Primary
public DataSource dataSourceA() throws SQLException {
...
}
#Bean(name="sqlSessionFactoryA")
#Primary
public SqlSessionFactory sqlSessionFactoryA() throws Exception {
...
}
Thanks to everyone who helped. No doubt, there's more than one way to do this. I did try #Qualifier and #EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class}) as recommended by #eduardlofitskyi and #GeminiKeith, but that generated some further errors.
In case it's useful, the solution that worked for us is posted here: https://github.com/alexwoolford/mybatis-spring-multiple-mysql-reproducible-example
You can use #Qualifier annotation
The problem is that you have two the same type beans in Spring container. And when you try autowire beans, Spring cannot resolve which bean inject to field
The #Qualifier annotation is the main way to work with qualifiers. It can be applied alongside #Autowired or #Inject at the point of injection to specify which bean you want to be injected.
So, your DbService should look like this:
#Service
public class DbService {
#Autowired
#Qualifier("dataSourceA")
private DbMapperA dbMapperA;
#Autowired
#Qualifier("dataSourceB")
private DbMapperB dbMapperB;
public List<Record> getDabaseARecords(){
return dbMapperA.getDatabaseARecords();
}
public List<Record> getDabaseBRecords(){
return dbMapperB.getDatabaseBRecords();
}
}
I had the same issue and could not start my Spring Boot application, and by renaming the offending class and all the layers that dealt with it, strangely the application started successfully.
I have the classes UOMService, UOMServiceImpl UOMRepository and UOMRepositoryImpl. I renamed them to be UomService, UomServiceImpl, UomRepository and UomRepositoryImpl and that solved the problem!