While starting a new project, I'm blocked because I'm not able to access the database via Spring and myBatis. And I do not understand why.
- I've setup my application.properties (by copying from another of my projects).
- I've setup my MyBatis-@Configuration via java-class (DefaultDataAccessConfig.java)
- I've setup my Spring-context via xml-file (also almost same as before).
- I've setup my MainClass also very same as before.
When accessing via MainClass I get an error.
When accessing via my JUnit5-Test I'm fine.
All spring setup works fine, classes are instantiated and can be accessed. But when it comes to storing the data the program fails because of missing dataSource and missing dataSourceClassName and missing jdbcUrl (I debugged it, neither is there). The very same method failing when called via MainClass is called by my Test. I have no clue, why myBatis is nother properly setup during start of MainClass.
I'm staring at the config and can't figure out whats wrong. This is how it looks like:
application.properties
server.port = 8192
logging.level.root=WARN
management.endpoints.web.exposure.include=*
management.endpoint.shutdown.enabled=true
endpoints.shutdown.enabled=true
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.jdbcUrl=jdbc:mysql://DESKTOP-IOL7CPB.fritz.box:3306/smarthome?useSSL=false&allowPublicKeyRetrieval=true
spring.datasource.username=*******
spring.datasource.password=*******
spring.datasource.maximum-pool-size=30
# Number of ms to wait before throwing an exception if no connection is available.
spring.datasource.tomcat.max-wait=60000
# Maximum number of active connections that can be allocated from this pool at the same time.
spring.datasource.tomcat.max-active=50
# Validate the connection before borrowing it from the pool.
spring.datasource.tomcat.test-on-borrow=true
DefaultDataAccessConfig.java
package de.gombers.smarthome.fritzbox.mybatis.configuration;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.TypeHandler;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import com.zaxxer.hikari.HikariDataSource;
@Configuration
@MapperScan({
"de.gombers.smarthome.fritzbox.mybatis"})
public class DefaultDataAccessConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(@Autowired @Qualifier("myDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sessionFactory.setDataSource(dataSource);
Resource[] resources = resolver.getResources("classpath*:mybatis/*-mapper.xml");
sessionFactory.setMapperLocations(resources);
@SuppressWarnings("rawtypes")
TypeHandler[] typeHandlers = { new CustomDateTypeHandler() };
sessionFactory.setTypeHandlers(typeHandlers);
return sessionFactory.getObject();
}
@Bean
@ConfigurationProperties("spring.datasource")
public HikariDataSource myDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean
public SqlSession sqlSession(@Autowired SqlSessionFactory sqlSessionFactory) {
return sqlSessionFactory.openSession();
}
}
/context/smartHome.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- enable autowire -->
<context:component-scan
base-package="de.gombers.smarthome" />
</beans>
Main class
@Service
public class GatherStatisticsMain {
private static final Logger LOGGER = LoggerFactory.getLogger(GatherStatisticsMain.class);
private final static String contextxml="/context/smartHome.xml";
@Autowired
private HomeAutomation homeAutomation;
@Autowired
private StoreDevices storeDevices;
public GatherStatisticsMain() {
}
public void process() throws InterruptedException {
final DeviceList devices = homeAutomation.getDeviceListInfos();
storeDevices.process(devices);
}
public static void main(String[] args) throws InterruptedException {
ApplicationContext context = new ClassPathXmlApplicationContext(contextxml);
LOGGER.info("------------" context.getBeanDefinitionCount());
GatherStatisticsMain main = (GatherStatisticsMain) context.getBean(GatherStatisticsMain.class);
main.process();
}
JUnit5-Test
@SpringBootTest
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:context/smartHome.xml")
@EnableAutoConfiguration
public class DevicesDAOTest {
private static final Logger LOGGER = LoggerFactory.getLogger(Tools.getSimpleClassName());
@Autowired
private DevicesDAO devicesDAO;
@Autowired
private DeviceBuilder devicesBuilder;
@Test
@DisplayName("Add new device")
public void addDeviceTest() throws DatatypeConfigurationException {
DeviceType device = devicesBuilder
.init()
.setIdentifier("Identifier")
.setManufacturer("Manufact")
.setName("Name")
.setProductname("ProductName")
.build();
try {
Optional<DeviceType> oDevice = devicesDAO.getDeviceByIdentifier(device.getIdentifier());
devicesDAO.deleteDeviceByIdentifier(device.getIdentifier());
} catch (Exception e) {
LOGGER.info("No previous occurence of device '{}' to be deleted", device.getIdentifier());
}
devicesDAO.insertDevice(device);
Long count = devicesDAO.getTotalRows();
Long result = 1L;
assertEquals("Size ok", result, count);
}
}
An the error I see is:
10:06:57.159 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession
10:06:57.159 [main] DEBUG org.mybatis.spring.SqlSessionUtils - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7bebcd65] was not registered for synchronization because synchronization is not active
10:06:57.163 [main] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Fetching JDBC Connection from DataSource
10:06:57.164 [main] ERROR com.zaxxer.hikari.HikariConfig - HikariPool-1 - dataSource or dataSourceClassName or jdbcUrl is required.
10:06:57.164 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7bebcd65]
Exception in thread "main" org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: java.lang.IllegalArgumentException: dataSource or dataSourceClassName or jdbcUrl is required.
### The error may exist in file [E:\$SysProg\GIT\SmallTools\SmartHome\target\classes\mybatis\Devices-mapper.xml]
### The error may involve de.gombers.smarthome.fritzbox.mybatis.objects.DevicesMapper.getDeviceByIdentifier
### The error occurred while executing a query
### Cause: java.lang.IllegalArgumentException: dataSource or dataSourceClassName or jdbcUrl is required.
at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:79)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:447)
at com.sun.proxy.$Proxy21.selectOne(Unknown Source)
at org.mybatis.spring.SqlSessionTemplate.selectOne(SqlSessionTemplate.java:167)
at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:82)
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:59)
at com.sun.proxy.$Proxy22.getDeviceByIdentifier(Unknown Source)
at de.gombers.smarthome.fritzbox.mybatis.objects.DevicesDAO.getDeviceByIdentifier(DevicesDAO.java:35)
at de.gombers.smarthome.fritzbox.mybatis.serializer.StoreDevices.isAlreadyPersisted(StoreDevices.java:56)
at de.gombers.smarthome.fritzbox.mybatis.serializer.StoreDevices.storeDevice(StoreDevices.java:44)
at de.gombers.smarthome.fritzbox.mybatis.serializer.StoreDevices.process(StoreDevices.java:39)
at de.gombers.smarthome.fritzbox.main.GatherStatisticsMain.process(GatherStatisticsMain.java:62)
at de.gombers.smarthome.fritzbox.main.GatherStatisticsMain.main(GatherStatisticsMain.java:78)
CodePudding user response:
You are using Spring Boot but are also trying very hard not to.
- Ditch your xml configuration
- Ditch your
DataSource
bean - Use
@SpringBootApplication
onGatherStatisticsMain
instead of@Service
- Fix your
main
to useSpringApplication.run
instead ofnew ApplicationContext
.
@Configuration
@MapperScan({
"de.gombers.smarthome.fritzbox.mybatis"})
public class DefaultDataAccessConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sessionFactory.setDataSource(dataSource);
Resource[] resources = resolver.getResources("classpath*:mybatis/*-mapper.xml");
sessionFactory.setMapperLocations(resources);
@SuppressWarnings("rawtypes")
TypeHandler[] typeHandlers = { new CustomDateTypeHandler() };
sessionFactory.setTypeHandlers(typeHandlers);
return sessionFactory.getObject();
}
@Bean
public SqlSession sqlSession(SqlSessionFactory sqlSessionFactory) {
return sqlSessionFactory.openSession();
}
}
NOTE: When using the MyBatis Spring Boot starter I think you can even ditch this configuration class.
@SpringBootApplication
public class GatherStatisticsMain {
private static final Logger LOGGER = LoggerFactory.getLogger(GatherStatisticsMain.class);
private final static String contextxml="/context/smartHome.xml";
@Autowired
private HomeAutomation homeAutomation;
@Autowired
private StoreDevices storeDevices;
public void process() throws InterruptedException {
final DeviceList devices = homeAutomation.getDeviceListInfos();
storeDevices.process(devices);
}
public static void main(String[] args) throws InterruptedException {
ApplicationContext context = SpringBootApplication.run(GatherStatisticsMain.class, args); LOGGER.info("------------" context.getBeanDefinitionCount());
GatherStatisticsMain main context.getBean(GatherStatisticsMain.class);
main.process();
}
}
Your test class should now look something like this.
@SpringBootTest
public class DevicesDAOTest { ... }
CodePudding user response:
Thanks to M. Deinum, who put me on the right track. I was not aware of mybatis-spring-boot-starter. As I had said, I had also tried to start the Main-class with @SpringBootApplication - and this had failed with the same error. Now I learned, that it failed, because the "DefaultDataAccessConfig.class". Right now I also can toggle between failing, just by introducing this class. The beans instantiated by this class seem to disturb in a way the correct setup.
Its all kind of magic. I removed
- /context/smartHome.xml
- DefaultDataAccessConfig.java
and ran the MainClass as suggested.