I am trying to use Spring JDBCTemplate class to access database.
As a first tutorial, I have used xml spring configuration file and everything works as expected.
Now, I am trying to use #Configuration and created DataSource and JdbcTemplate instances through #Bean annotation within this file. But, I get a NullPointerException at int result = template.update(sql);
I am sure I have done a silly mistake. Was wondering what it could be.
The code is as follows.
#Configuration
public class SpringJDBCAnnotation {
#Autowired
static JdbcTemplate template;
#Autowired
static DataSource dataSource;
#Bean
DataSource dataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/organization");
ds.setUsername("root");
ds.setPassword("test");
return ds;
}
#Bean
JdbcTemplate template() {
JdbcTemplate template = new JdbcTemplate();
template.setDataSource(dataSource);
return template;
}
public static void main(String[] args) {
String sql = "insert into employee values(1, 'Tom', 'Cruise')";
int result = template.update(sql);
System.out.println("# of records inserted : " + result);
}
}
Application Context needs to be obtained whenever we need to create beans or autowire them.
Since this is Java based configuration, it is retrieved as follows.
ApplicationContext context = new AnnotationConfigApplicationContext(SpringJDBCAnnotation.class);
and the beans needs to be accessed from the context as usual.
JdbcTemplate template = context.getBean(JdbcTemplate.class);
Related
I have two databases I need instances of at the same time, they are running on a oracle weblogic server rather than locally. In a configurator class, I have specified the sources and tested the connection and both DBs pull data properly. (When used one at a time, or with one as #Primary)
#Configuration
public class appDatabaseConfiguration {
#Bean
#Primary
public DataSource dataSourceA() throws Exception{
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
JndiTemplate corpdwJndiTemplate = new JndiTemplate();
return dataSourceLookup.getDataSource("<DBAProperties>");
}
#Bean
public DataSource dataSourceB() throws Exception{
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
JndiTemplate jndiTemplate = new JndiTemplate();
return dataSourceLookup.getDataSource("<DBBProperties>");
}
}
However, I am unable to use #Qualifier to tell the instantiation which bean in the configuration to select-
#Service
public class DaoImpl implements Dao{
#Autowired
#Qualifier("dataSourceA")
private JdbcTemplate dataSourceA;
#Autowired
#Qualifier("dataSourceB")
private JdbcTemplate dataSourceB;
public String getData() {
resultsA = dataSourceA.queryForObject("SELECT COUNT(*) FROM TABLE", String.class);
resultsB = dataSourceB.queryForObject("SELECT COUNT(*) FROM TABLE", String.class);
return resultsA + resultsB;
}
}
When #Qualifier is not used:
DataSourceA Query will succeed, but DataSourceB will fail (Table or View doesn't exist error)- because it is pulling the #Primary bean for both instantiations
When #Qualifier is used:
Application errors with-
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type
'org.springframework.jdbc.core.JdbcTemplate' available: expected at least 1 bean which
qualifies as autowire candidate. Dependency annotations:
{#org.springframework.beans.factory.annotation.Autowired(required=dataSourceA)}
I have tried adding specific names to the bean like #Bean("dataSourceA"), #Bean(name="dataSourceB") rather than relying on the function name and several other syntax changes but no results. Does anyone have some insight here?
Note: Even when one datasource bean is commented out, and the other has only a name and not #primary it still throws the same autowire bean error, so I don't believe that the annotation is functioning properly in my project (or I'm using it wrong).
You'll have to create two JdbcTemplates each configured with a different datasource and autowire with the qualifier of the template, for example with
#Bean
#Primary
public JdbcTemplate jdbcTemp1(#Qualifier("dataSourceA") DataSource ds) {
return new JdbcTemplate(ds);
}
#Bean
public JdbcTemplate jdbcTemp2(#Qualifier("dataSourceB") DataSource ds) {
return new JdbcTemplate(ds);
}
...
#Autowired
#Qualifier("jdbcTemp2")
private JdbcTemplate jdbcTemplate;
It's because the type of your beans defined in the #Configuration is DataSource and in your #Service you inject beans of type JdbcTemplate.
To make it work, you should create two more beans of type JdbcTemplate using your two DataSource like this :
#Configuration
public class appDatabaseConfiguration {
#Bean
#Primary
public DataSource dataSourceA() throws Exception{
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
JndiTemplate corpdwJndiTemplate = new JndiTemplate();
return dataSourceLookup.getDataSource("<DBAProperties>");
}
#Bean
public DataSource dataSourceB() throws Exception{
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
JndiTemplate jndiTemplate = new JndiTemplate();
return dataSourceLookup.getDataSource("<DBBProperties>");
}
#Bean(name="jdbcTemplateA")
public JdbcTemplate jdbcTemplateA() throws Exception {
return new JdbcTemplate(dataSourceA());
}
#Bean(name="jdbcTemplateB")
public JdbcTemplate jdbcTemplateB() throws Exception {
return new JdbcTemplate(dataSourceB());
}
}
And then, inject your beans using #Qualifier("jdbcTemplateA") or #Qualifier("jdbcTemplateB")
I just started learning Spring, and now I try to crate Spring JDBC based DAO application.
I created config class in this way
#Configuration
#ComponentScan("com.foxminded.university")
public class SpringJdbcConfig {
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.postgresql.Driver");
dataSource.setUrl("jdbc:postgresql://127.0.0.1:5432/university");
dataSource.setUsername("maintainer");
dataSource.setPassword("12345678");
return dataSource;
}
}
And dao-class uses this bean
#Component
public class BuildingDao implements Dao<Building> {
#Autowired
private DataSource dataSource;
private final JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
private static final String SAVE_BUILDING = "Insert into buildings (name, floors) values (?,?)";
#Override
public void save(Building building) {
jdbcTemplate.update(SAVE_BUILDING, building.getName(), building.getFloors());
}
}
But when I try to run this query i get
Exception in thread "main" java.lang.IllegalArgumentException: Property 'dataSource' is required
How I can fix it? As I can see, I use #Autowired incorrectly, because everything works fine when I use
private DataSource dataSource = new SpringJdbcConfig().dataSource();
But it is extra relation and mistake in terms of IoC.
By the way in main I also have to use this in this way
public class Main {
public static void main(String[] args) {
Building building = new SpringJdbcConfig().building();
building.setName("hghgf");
building.setFloors(2);
BuildingDao buildingDao = new SpringJdbcConfig().buildingDao();
buildingDao.save(building);
}
}
I would be very grateful if you could explain how to use #autowired correctly and inject beans into the main class.
I would suggest you to use spring boot to configure your application as below. This will initialize and auto-configure most of your needs.
#SpringBootApplication
#EnableAutoConfiguration
#ComponentScan("com.foxminded.university")
public class SpringBootWebApp {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringBootWebApp.class, args);
context.registerShutdownHook();
}
}
After this, you can use #Autowire for all those spring managed beans you configure.
Write the ComponentScan attribute as follows and make sure SpringJdbcConfig class is under the package com.foxminded.university*
#ComponentScan(basePackages = "com.foxminded.university")
I have a Spring Boot application that uses 2 datasources. It can connect to the first one fine. When I try to use the second data source, I am getting the SQL error: Table or view does not exist because it is using the first data source as its connection.
This is my application property file:
#Property for both DBs
spring.datasource.driver.class.name=oracle.jdbc.driver.OracleDriver
## Database Properties DB #1
spring.datasource.tms.url=jdbc:oracle:thin:#ldap:<connection properties have been removed but are present>
spring.datasource.tms.username=own_app
spring.datasource.tms.password=own_app_l1
spring.datasource.tms.jmx.enabled=true
spring.main.allow-bean-definition-overriding=true
## Database Properties DB #2
spring.datasource.lhl.url=jdbc:oracle:thin:#ldap:<connection string has been removed but is correct>
spring.datasource.lhl.username=LHL_PURCH_APP
spring.datasource.lhl.password=ChangemeChangemeChangeme$$2019
spring.datasource.lhl.jmx-enabled=true
This is my Configuration file for both data sources:
#Configuration
#PropertySource("classpath:application-local.properties")
public class FxgLhlPurchasedItineraryAdapterDataSourceConfiguration {
#Value("${spring.datasource.driver.class.name}")
private String driverClassName;
//TMS properties
#Value("${spring.datasource.tms.url}")
private String tmsUrl;
#Value("${spring.datasource.tms.username}")
private String tmsUsername;
#Value("${spring.datasource.tms.password}")
private String tmsPassword;
//LHL Properties
#Value("${spring.datasource.lhl.url}")
private String lhlUrl;
#Value("${spring.datasource.lhl.username}")
private String lhlUsername;
#Value("${spring.datasource.lhl.password}")
private String lhlPassword;
#Primary
#Bean(name = "tmsDataSource")
#ConfigurationProperties(prefix = "spring.datasource.tms")
public DataSource tmsDataSource() {
DataSourceBuilder factory = DataSourceBuilder.create(this.getClass().getClassLoader())
.driverClassName(driverClassName).url(tmsUrl)
.username(tmsUsername)
.password(tmsPassword);
return factory.build();
}
#Bean(name = "lhlDataSource")
#ConfigurationProperties(prefix = "spring.datasource.lhl")
public DataSource lhlDataSource() {
DataSourceBuilder factory = DataSourceBuilder.create(this.getClass().getClassLoader())
.driverClassName(driverClassName).url(lhlUrl)
.username(lhlUsername)
.password(lhlPassword);
return factory.build();
}
#Bean(name = "tmsJdbcTemplate")
public JdbcTemplate tmsJdbcTemplate(final DataSource tmsDataSource) {
return new JdbcTemplate(tmsDataSource);
}
#Bean(name = "lhlJdbcTemplate")
public JdbcTemplate lhlJdbcTemplate(final DataSource lhlDataSource) {
return new JdbcTemplate(lhlDataSource);
}
}
I had to put in the #Primary annotation if the service will not run.
When I try to do a simple SELECT of a table in that is not the Primary database, I get the error that the table does not exist.
This is the code that calls the select statement:
private JdbcTemplate lhlJdbcTemplate;
DataSource ds = lhlJdbcTemplate.getDataSource();
Connection con = ds.getConnection();
LOGGER.info("Connection info: {}", con.getSchema());
lhlParmSqIdModelList = lhlJdbcTemplate.query(
selectSequenceNbrSQLStatement,
new LhlParmSqIdModelRowMapper());
The logger statement returns the schema for the Primary database.
How can I get the connection to use the Second database
Because you have multiple DataSource beans, normally Spring will fail because it doesn't know how to automatically decide which of multiple equivalent beans it should use, it puts that responsibility on you as the programmer.
By adding the #Primary annotation, you are telling Spring "If there are multiple candidate beans of this type, use this one."
Your bean methods aren't asking Spring for a particular DataSource, they just want any DataSource, so Spring gives each of them the one marked with #Primary.
Instead, you'll want to use #Qualifier to indicate exactly which named DataSource they want:
#Bean(name = "tmsJdbcTemplate")
public JdbcTemplate tmsJdbcTemplate(#Qualifier("tmsDataSource") final DataSource tmsDataSource) {
return new JdbcTemplate(tmsDataSource);
}
#Bean(name = "lhlJdbcTemplate")
public JdbcTemplate lhlJdbcTemplate(#Qualifier("lhlDataSource") final DataSource lhlDataSource) {
return new JdbcTemplate(lhlDataSource);
}
I don't guarantee this syntax is exactly right, but something like that.
You'll also need to qualify the JdbcTemplate injection point. (Credit to Sherif Behna in the comments)
I'm writing application which connects with Oracle Database. I call function from DB which inserts new records to table. And after this callback I can decide what I want to do: commit or rollback.
Unfortunalety I'm new in Spring, so I have problems with configuration. And what's more I want to make this configuration in Java class, not in XML. And here I need your help.
UPDATED CODE:
ApplicationConfig code:
#Configuration
#EnableTransactionManagement
#ComponentScan("hr")
#PropertySource({"classpath:jdbc.properties", "classpath:functions.properties", "classpath:procedures.properties"})
public class ApplicationConfig {
#Autowired
private Environment env;
#Bean(name="dataSource")
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(env.getProperty("jdbc.driver"));
dataSource.setUrl(env.getProperty("jdbc.url"));
dataSource.setUsername(env.getProperty("jdbc.username"));
dataSource.setPassword(env.getProperty("jdbc.password"));
dataSource.setDefaultAutoCommit(false);
return dataSource;
}
#Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
return jdbcTemplate;
}
#Bean(name="txName")
public PlatformTransactionManager txManager() {
DataSourceTransactionManager txManager = new DataSourceTransactionManager();
txManager.setDataSource(dataSource());
return txManager;
}
}
I have Dao and Service, where both implements proper interface.
Service implementation:
#Service
public class HumanResourcesServiceImpl implements HumanResourcesService {
#Autowired
private HumanResourcesDao hrDao;
#Override
public String generateData(int rowsNumber) {
return hrDao.generateData(rowsNumber);
}
#Override
#Transactional("txName")
public void shouldCommit(boolean doCommit, Connection connection) throws SQLException {
hrDao.shouldCommit(doCommit, connection);
}
}
Dao implementation:
#Repository
public class HumanResourcesDaoImpl implements HumanResourcesDao {
private JdbcTemplate jdbcTemplate;
private SimpleJdbcCall generateData;
#Autowired
public HumanResourcesDaoImpl(JdbcTemplate jdbcTemplate, Environment env) {
this.jdbcTemplate = jdbcTemplate;
generateData = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName(env.getProperty("procedure.generateData"));
}
#Override
public String generateData(int rowsNumber) {
HashMap<String, Object> params = new HashMap<>();
params.put("i_rowsNumber", rowsNumber);
Map<String, Object> m = generateData.execute(params);
return (String) m.get("o_execution_time");
}
#Override
#Transactional("txName")
public void shouldCommit(boolean doCommit, Connection connection) throws SQLException {
if(doCommit) {
connection.commit();
} else {
connection.rollback();
}
}
}
Main class code:
public class Main extends Application implements Initializable {
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
hrService = context.getBean(HumanResourcesService.class);
BasicDataSource ds = (BasicDataSource)context.getBean("dataSource");
Connection connection = ds.getConnection();
//do something and call
//hrService.generateData
//do something and call
//hrService.shouldCommit(true, connection);
//which commit or rollback generated data from previoues callback
}
}
UPDATE:
I think that the problem is with connection, because this statement:
this.jdbcTemplate.getDataSource().getConnection();
creates new connection, so then there is nothing to commit or rollback. But still I can't figure why this doesn't work properly. No errors, no new records...
What is wierd, is that when I debuged connection.commit(); I found out that in DelegatingConnection.java, parameter this has proper connection, but there is something like:
_conn.commit();
and _conn has different connection. Why?
Should I in some way synchronize connection for those 2 methods or what? Or this is only one connection? To be honest, I'm not sure how it works exactly. One connection and all callbacks to stored procedures are in this connection or maybe with each callback new connection is created?
Real question is how to commit or rollback data from previous callback which do insert into table?
One easy way to do this is to annotate the method with #Transactional:
#Transactional
public void myBeanMethod() {
...
if (!doCommit)
throw new IllegalStateException(); // any unchecked will do
}
and spring will roll all database changes back.
Remember to add #EnableTransactionManagement to your spring application/main class
You can use #Transactional and #EnableTransactionManagement to setup transactions without using the XML configuration. In short, annotate the methods/classes you want to have transactions with #Transactional. To setup the transactional management you use the #EnableTransactionManagement inside your #Configuration.
See Springs docs for example on how to use both. The #EnableTransactionManagement is detailed in the JavaDocs but should match the XML configuration.
UPDATE
The problem is that you are mixing raw JDBC calls (java.sql.Connection) with Spring JDBC. When you execute your SimpleJdbcCall, Spring creates a new Connection. This is not the same Connection as the one you later try to commit. Hence, nothing happens when you perform the commit. I tried to somehow get the connection that the SimpleJdbcCall uses, but could not find any easy way.
To test this I tried the following (I did not use params):
#Override
public String generateData(int rowsNumber) {
//HashMap<String, Object> params = new HashMap<>();
//params.put("i_rowsNumber", rowsNumber);
//Map<String, Object> m = generateData.execute(params);
Connection targetConnection = DataSourceUtils.getTargetConnection(generateData.getJdbcTemplate().getDataSource().getConnection());
System.out.println(targetConnection.prepareCall((generateData.getCallString())).execute());
targetConnection.commit();
return (String) m.get("o_execution_time");
}
If I don't save the targetConnection, and instead try to get the connection again by calling DataSourceUtils.getTargetConnection() when committing, nothing happens. Thus, you must commit on the same connection that you perform the statement on. This does not seem to be easy, nor the proper way.
The solution is to drop the java.sql.Connection.commit() call. Instead, you use Spring Transactions completly. If you use #Transaction on the method that performs database call, Spring will automatically commit when the method has finished. If the method body experiences any Exception (even outside the actual database call) it will automatically rollback. In other words, this should suffice for normal Transaction management.
However, if you are doing batch processing, and wish to have more control over your transactions with commits and rollbacks, you can still use Spring. To programatically control transactions with Spring, you can use TransactionTemplate which have commit and rollback methods. Don't have time to give you proper samples, but may do so in later days if you are still stuck ;)
#Configuration
#EnableTransactionManagement
#ComponentScan(basePackages="org.saat")
#PropertySource(value="classpath:resources/db.properties",ignoreResourceNotFound=true)
public class AppConfig {
#Autowired
private Environment env;
#Bean(name="dataSource")
public DataSource getDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("db.driver"));
dataSource.setUrl(env.getProperty("db.url"));
dataSource.setUsername(env.getProperty("db.username"));
dataSource.setPassword(env.getProperty("db.password"));
return dataSource;
}
#Bean(name="entityManagerFactoryBean")
public LocalContainerEntityManagerFactoryBean getSessionFactory() {
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean ();
factoryBean.setDataSource(getDataSource());
factoryBean.setPackagesToScan("org.saat");
factoryBean.setJpaVendorAdapter(getJpaVendorAdapter());
Properties props=new Properties();
props.put("hibernate.dialect", env.getProperty("hibernate.dialect"));
props.put("hibernate.hbm2ddl.auto",env.getProperty("hibernate.hbm2ddl.auto"));
props.put("hibernate.show_sql",env.getProperty("hibernate.show_sql"));
factoryBean.setJpaProperties(props);
return factoryBean;
}
#Bean(name="transactionManager")
public JpaTransactionManager getTransactionManager() {
JpaTransactionManager jpatransactionManager = new JpaTransactionManager();
jpatransactionManager.setEntityManagerFactory(getSessionFactory().getObject());
return jpatransactionManager;
}
#Bean
public JpaVendorAdapter getJpaVendorAdapter() {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
return hibernateJpaVendorAdapter;
}
}
I got Spring Java Config style configuration with 2 datasources:
#Configuration
#EnableTransactionManagement
public class DBConfig {
// main db
#Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:schema.sql")
.addScript("classpath:data.sql")
.build();
}
//db for test
#Bean(name = "testDataSource")
public DataSource testDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}
#Bean
public JdbcTemplate jdbcTemplate(){
return new JdbcTemplate(dataSource());
}
But when I autowire that datasources in my Test class and run him: i got the same result:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = DBConfig.class)
#Transactional
public class JdbcTest {
#Autowired
private JdbcTemplate jdbcTemplate;
#Autowired
#Qualifier("testDataSource")
private DataSource testDataSource;
#Test
public void findRealDb() {
String rowCount = jdbcTemplate.queryForObject("select message from messages", String.class);
System.out.println("real db " + rowCount);
}
#Test
public void findTestDb() {
String rowCount = (new JdbcTemplate(testDataSource).queryForObject("select message from messages", String.class));
System.out.println("test db " + rowCount);
}
}
So as result method findTestDb() logs same rowCount string as findRealDb() , but as you see them use different datasources to build jdbcTemplate.
The test code will be autowiring by type. I'm surprised you don't get a non-unique bean exception.
The trouble is you have two beans, one of which is qualified and one of which isn't.
You are much better off using Spring profiles and assigning the test datasource to a test profile and the production datasource to a default profile, then setting the active profile for your test case to be test.
Here's an example:
http://fahdshariff.blogspot.co.uk/2012/09/spring-3-javaconfig-unit-testing-using.html
note that you can put the #Profile annotation on an individual bean or a configuration class.