(Edit: I've added a bounty to the question. I found a workaround (posted as an answer below), but I'm hoping somebody can explain why the workaround was necessary in the first place.)
I have a Spring webapp that connects to a Derby database during development. This works fine the first time I run the webapp, but in subsequent runs it fails during startup with the "Another instance of Derby may have already booted the database" SQLException.
I understand that this is because the connection to Derby isn't being closed when I shutdown Tomcat, even though I would expect Spring to handle that automatically. So my question is, how do I disconnect from Derby correctly? Not only during manually stopping Tomcat, but also during hot deploying of a new .war file?
I'd like to avoid using a Derby server, and I'm also using annotations instead of XML configuration. Here was my original PersistConfig class:
package com.example.spring.config;
import java.beans.PropertyVetoException;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.derby.jdbc.EmbeddedDataSource;
import org.hibernate.SessionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import org.springframework.jdbc.datasource.embedded.ConnectionProperties;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseConfigurer;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.hibernate4.HibernateExceptionTranslator;
import org.springframework.orm.hibernate4.HibernateTransactionManager;
import org.springframework.orm.hibernate4.LocalSessionFactoryBean;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;
#Configuration
#ComponentScan({"com.example.spring.dao.jpa"})
#EnableTransactionManagement // <-- enable #Transactional annotations for spring #Component and stereotypes
public class PersistConfig{
#Bean
public HibernateExceptionTranslator exceptionTranslator() {
return new HibernateExceptionTranslator();
}
#Bean
public LocalSessionFactoryBean localSessionFactoryBean(DataSource dataSource, JpaVendorAdapter vendorAdapter) {
LocalSessionFactoryBean localSessionFactoryBean = new LocalSessionFactoryBean();
localSessionFactoryBean.setDataSource(dataSource);
localSessionFactoryBean.setPackagesToScan("com.example.one", "com.example.two");
Properties properties = new Properties();
properties.putAll(vendorAdapter.getJpaPropertyMap());
localSessionFactoryBean.setHibernateProperties(properties);
return localSessionFactoryBean;
}
#Bean
public HibernateTransactionManager hibernateTransactionManager(SessionFactory sessionFactory) {
HibernateTransactionManager hibernateTransactionManager = new HibernateTransactionManager(sessionFactory);
return hibernateTransactionManager;
}
#Configuration
public static class DevelopmentConfig{
#Bean
public DataSource dataSource() throws SQLException, PropertyVetoException {
DataSource dataSource = new SimpleDriverDataSource(new org.apache.derby.jdbc.EmbeddedDriver(), "jdbc:derby:C:\\Users\\Kevin\\Desktop\\DerbyDB", "", "");
System.out.println("RETURNING DATASOURCE");
return dataSource;
}
#Bean
JpaVendorAdapter vendorAdapter() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.DERBY);
vendorAdapter.setDatabasePlatform("org.hibernate.dialect.DerbyDialect");
vendorAdapter.setGenerateDdl(true);
vendorAdapter.setShowSql(true);
vendorAdapter.getJpaPropertyMap().put("hibernate.hbm2ddl.auto", "update");
vendorAdapter.getJpaPropertyMap().put("hbm2ddl.auto", "update");
return vendorAdapter;
}
}
}
I tried adding a shutdown hook to the whole JVM using Runtime.addShutdownHook() where I manually disconnect from the Derby database, but that is seemingly never fired.
I was then told to look into the EmbeddedDatabaseConfigurer interface to add a Spring shutdown callback where I manually close the database connection, and this is what I came up with:
package com.example.spring.config;
import java.beans.PropertyVetoException;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.derby.jdbc.EmbeddedDataSource;
import org.hibernate.SessionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import org.springframework.jdbc.datasource.embedded.ConnectionProperties;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseConfigurer;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.hibernate4.HibernateExceptionTranslator;
import org.springframework.orm.hibernate4.HibernateTransactionManager;
import org.springframework.orm.hibernate4.LocalSessionFactoryBean;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;
#Configuration
#ComponentScan({"com.example.spring.dao.jpa"})
#EnableTransactionManagement // <-- enable #Transactional annotations for spring #Component and stereotypes
public class PersistConfig implements EmbeddedDatabaseConfigurer {
#Bean
public HibernateExceptionTranslator exceptionTranslator() {
return new HibernateExceptionTranslator();
}
#Bean
public LocalSessionFactoryBean localSessionFactoryBean(DataSource dataSource, JpaVendorAdapter vendorAdapter) {
LocalSessionFactoryBean localSessionFactoryBean = new LocalSessionFactoryBean();
localSessionFactoryBean.setDataSource(dataSource);
localSessionFactoryBean.setPackagesToScan("com.example.one", "com.example.two");
Properties properties = new Properties();
properties.putAll(vendorAdapter.getJpaPropertyMap());
localSessionFactoryBean.setHibernateProperties(properties);
return localSessionFactoryBean;
}
#Bean
public HibernateTransactionManager hibernateTransactionManager(SessionFactory sessionFactory) {
HibernateTransactionManager hibernateTransactionManager = new HibernateTransactionManager(sessionFactory);
return hibernateTransactionManager;
}
#Configuration
public static class DevelopmentConfig{
#Bean
public DataSource dataSource() throws SQLException, PropertyVetoException {
DataSource dataSource = new SimpleDriverDataSource(new org.apache.derby.jdbc.EmbeddedDriver(), "jdbc:derby:C:\\Users\\Kevin\\Desktop\\DerbyDB", "", "");
System.out.println("RETURNING DATASOURCE");
return dataSource;
}
#Bean
JpaVendorAdapter vendorAdapter() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.DERBY);
vendorAdapter.setDatabasePlatform("org.hibernate.dialect.DerbyDialect");
vendorAdapter.setGenerateDdl(true);
vendorAdapter.setShowSql(true);
vendorAdapter.getJpaPropertyMap().put("hibernate.hbm2ddl.auto", "update");
vendorAdapter.getJpaPropertyMap().put("hbm2ddl.auto", "update");
return vendorAdapter;
}
}
#Override
public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
System.out.println("CONFIGURE");
properties.setDriverClass(org.apache.derby.jdbc.EmbeddedDriver.class);
properties.setUrl("jdbc:derby:C:\\Users\\Kevin\\Desktop\\DerbyDB");
}
#Override
public void shutdown(DataSource ds, String databaseName) {
System.out.println("SHUTTING DOWN");
try {
DriverManager.getConnection("jdbc:derby:C:\\Users\\Kevin\\Desktop\\DerbyDB;shutdown=true");
}
catch (SQLException e) {
e.printStackTrace();
}
}
}
However, neither the configureConnectionProperties() function nor the shutdown() function seem to be called. I obviously don't know what I'm doing, so any pointers are greatly appreciated.
EDIT : add a precision of restarting embedded Derby database and a probably simpler solution.
I could reproduce at least partially the problem, understand it and fix it. But I cannot say why BoneCP works fine. I simply noticed that if I waited enough between shutting down tomcat and restarting it again, it worked. I suppose that BoneCP do not access immediately the database, and waits enough until first real connection.
First the problem : when using Derby as an embedded database, the database is booted at first connection, but it has to be explicitely shutted down. If it is not, the db.lock file is not deleted and further application may experience problems booting the database again. Nothing exists either in tomcat or (by default) in spring to automatically shutdown such a database.
Next, why your attempt using an EmbeddedDatabaseConfigurer didn't work : EmbeddedDatabaseConfigurer is not a magic marker and inheriting it in a class is not enough to have spring automatically use it. It is simply an interface that must be implemented by a configurer to allow an EmbeddedDatabaseFactory to use it.
Finally the fix. You should not use SimpleDriverDataSource to get your connections from an embedded Derby database but an EmbeddedDatabaseFactory. Spring by default knows Derby embedded database, and you can configure the factory by simply setting the type ... but that only works for in memory databases and you have a file database ! Would have been too simple ... You must inject the factory with a configurer to have all being ok.
And now the code (starting from your first version) :
#Configuration
public static class DevelopmentConfig{
EmbeddedDatabaseFactory dsFactory;
public DevelopmentConfig() {
EmbeddedDatabaseConfigurer configurer = new EmbeddedDatabaseConfigurer() {
#Override
public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
System.out.println("CONFIGURE");
properties.setDriverClass(org.apache.derby.jdbc.EmbeddedDriver.class);
properties.setUrl("jdbc:derby:C:\\Users\\Kevin\\Desktop\\DerbyDB");
}
#Override
public void shutdown(DataSource dataSource, String databaseName) {
final String SHUTDOWN_CODE = "XJ015";
System.out.println("SHUTTING DOWN");
try {
DriverManager.getConnection("jdbc:derby:;shutdown=true");
} catch (SQLException e) {
// Derby 10.9.1.0 shutdown raises a SQLException with code "XJ015"
if (!SHUTDOWN_CODE.equals(e.getSQLState())) {
e.printStackTrace();;
}
}
}
};
dsFactory = new EmbeddedDatabaseFactory();
dsFactory.setDatabaseConfigurer(configurer);
}
#Bean
public DataSource dataSource() throws SQLException, PropertyVetoException {
System.out.println("RETURNING DATASOURCE");
return dsFactory.getDatabase();
}
// remaining of code unchanged
This way, I can hot reload the war, and when tomcat is closed, the db.lock is normally destroyed.
Edit:
In case of problems, Derby documentation advices to add the following command to restart a database after a shutdown : Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance(); . It could be the last instruction of the configureConnectionProperties method.
But in fact, the solution could be even simpler. What really needs to be added to your config is a proper shutdown of the embedded driver (and eventually a restart). So a simple PreDestroy (and eventualy `#PostConstruct) annotated method should be enough :
#Configuration
public static class DevelopmentConfig{
#PreDestroy
public void shutdown() {
final String SHUTDOWN_CODE = "XJ015";
System.out.println("SHUTTING DOWN");
try {
DriverManager.getConnection("jdbc:derby:;shutdown=true");
} catch (SQLException e) {
// Derby 10.9.1.0 shutdown raises a SQLException with code "XJ015"
if (!SHUTDOWN_CODE.equals(e.getSQLState())) {
e.printStackTrace();
}
}
}
/* if needed ...
#PostConstruct
public void init() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance();
}
*/
#Bean
public DataSource dataSource() throws SQLException, PropertyVetoException {
DataSource dataSource = new SimpleDriverDataSource(new org.apache.derby.jdbc.EmbeddedDriver(), "jdbc:derby:C:\\Users\\Kevin\\Desktop\\DerbyDB", "", "");
System.out.println("RETURNING DATASOURCE");
return dataSource;
}
// remaining of code unchanged
The main interest of this variant is that you can choose your datasource, from a SimpleDriverDataSource to a real pool.
I figured out a fix to the problem, although I don't really understand why this fix works. It turns out that using BoneCP to configure the DataSource fixes the problem, or at least covers it up.
public DataSource dataSource() throws SQLException, PropertyVetoException {
BoneCPConfig config = new BoneCPConfig();
config.setUsername("");
config.setPassword("");
config.setJdbcUrl("jdbc:derby:C:\\Users\\Kevin\\Desktop\\DerbyDB");
BoneCPDataSource dataSource = new BoneCPDataSource(config);
dataSource.setDriverClass("org.apache.derby.jdbc.EmbeddedDriver");
return dataSource;
}
Stranger still, the db.lck file is still never deleted, but I don't see any errors at all, and it seems to be working fine.
I'm adding this as an answer in case somebody else has a similar problem, but I'm leaving the question open in case somebody can explain to me why this fixes the problem.
You can use a LifeCycleListener which executes shutdown procedure of Derby if you defined Embedded Derby as a DataSource (Resource) of Tomcat.
Here's a sample implementation of LifeCycleListener for Tomcat8, and detail of setup.
Related
I want to perform unit testing in spring boot+ JPA. For that I created configuration file to create bean for dataSource, all hibernate properties, entityManagerFactory and transactionManager. Everything going perfect. Tables are getting created by model classes. but now I want to insert data in all tables of database for testing through data.sql file.
I kept data.sql file in src/main/resources but it not loading the file.
So how can I load data in h2 database before starting with unit testing.
This is my cofiguration file -
import java.util.Properties;
import javax.sql.DataSource;
import org.hibernate.cfg.Environment;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
#Configuration
#EnableJpaRepositories(basePackages = "base_package_name")
#EnableTransactionManagement
public class JPAConfig {
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl("jdbc:h2:mem:myDb;DB_CLOSE_DELAY=-1");
/*dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("jdbc:hsqldb:mem:testdb");*/
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}
private Properties hibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
properties.put("hibernate.hbm2ddl.auto", "create");
properties.put("hibernate.show_sql", "true");
properties.put("hibernate.format_sql", "false");
return properties;
}
#Bean(name="entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(){
LocalContainerEntityManagerFactoryBean lcemfb
= new LocalContainerEntityManagerFactoryBean();
lcemfb.setDataSource(this.dataSource());
lcemfb.setPackagesToScan(new String[] {"Package_to_scan"});
HibernateJpaVendorAdapter va = new HibernateJpaVendorAdapter();
lcemfb.setJpaVendorAdapter(va);
lcemfb.setJpaProperties(this.hibernateProperties());
lcemfb.afterPropertiesSet();
return lcemfb;
}
#Bean
public PlatformTransactionManager transactionManager(){
JpaTransactionManager tm = new JpaTransactionManager();
tm.setEntityManagerFactory(
this.entityManagerFactoryBean().getObject() );
return tm;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
}
According to another question on StackOverflow, you can initialise a database by adding a #Configuration class to your tests, as follows:
#Configuration
public class DatabaseTestConfig {
#Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}
}
To note that the above doesn't use JPA so you may have to adjust it for your own purposes, but it should give a good hint on how you could do it in JPA as well.
My preference however leans towards using the #Sql annotation in each test, to initialise the data and then to clean it up. While this means more repetition, which is usually bad, it helps ensuring that tests always run from a clean slate.
References
How to initialize in-memory HSQLDB using script via Spring
https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/testing.html#spring-testing-annotation-sql
https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto-initialize-a-database-using-hibernate
Before Spring Batch job runs. I have a import table which contains all items that needs importing into our system. It is at this point verified to contain only items that does not exist in our system.
Next I have a Spring Batch Job, which reads from this import table using JpaPagingItemReader.
After work is done, it writes to db using ItemWriter.
I run with page-size and chunk-size at 10000.
Now this works absolutely fine when running on MySQL innoDB. I can even use multiple threading and everything works fine.
But now we are migrating to PostgreSQL, and the same Batch Job runs into some very strange problem
What happens is that it tries to insert duplicates into our system. This will naturally be rejected by unique index constraints and an error is thrown.
Since the import db table is verified to contain only non-existing before batch job starts, the only reason for this i can think of is that the JpaPagingItemReader reads some rows multiple times from import db table when i run on Postgres. But why would it do that?
I have experimented with a lot of settings. Turning chunk and page-size down to around 100 only makes import slower, but still same error. Running single-thread instead of multiple threads only makes the error happen slightly later.
So what on earth could be the reason for my JpaPagingItemReader reading the same items multiple times only on PostgresSQL?
The select statement backing the reader is simple, its a NamedQuery:
#NamedQuery(name = "ImportDTO.findAllForInsert",
query = "select h from ImportDTO h where h.toBeImported = true")
Please also note that the toBeImported flag will not be altered by the batch job at all during runtime, so the results from this query should always return the same before, under and after the batch job.
Any insights, tips or help is greatly appriciated!
Here is Batch Config code:
import javax.persistence.EntityManagerFactory;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.launch.support.SimpleJobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.item.database.JpaPagingItemReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
#Configuration
#EnableBatchProcessing
public class BatchConfiguration {
#Autowired
private JobBuilderFactory jobBuilderFactory;
#Autowired
private StepBuilderFactory stepBuilderFactory;
#Autowired
private OrganizationItemWriter organizationItemWriter;
#Autowired
private EntityManagerFactory entityManagerFactory;
#Autowired
private OrganizationUpdateProcessor organizationUpdateProcessor;
#Autowired
private OrganizationInsertProcessor organizationInsertProcessor;
private Integer organizationBatchSize = 10000;
private Integer organizationThreadSize = 3;
private Integer maxThreadSize = organizationThreadSize;
#Bean
public SimpleJobLauncher jobLauncher(JobRepository jobRepository) {
SimpleJobLauncher launcher = new SimpleJobLauncher();
launcher.setJobRepository(jobRepository);
return launcher;
}
#Bean
public JpaPagingItemReader<ImportDTO> findNewImportsToImport() throws Exception {
JpaPagingItemReader<ImportDTO> databaseReader = new JpaPagingItemReader<>();
databaseReader.setEntityManagerFactory(entityManagerFactory);
JpaQueryProviderImpl<ImportDTO> jpaQueryProvider = new JpaQueryProviderImpl<>();
jpaQueryProvider.setQuery("ImportDTO.findAllForInsert");
databaseReader.setQueryProvider(jpaQueryProvider);
databaseReader.setPageSize(organizationBatchSize);
// must be set to false if multi threaded
databaseReader.setSaveState(false);
databaseReader.afterPropertiesSet();
return databaseReader;
}
#Bean
public JpaPagingItemReader<ImportDTO> findImportsToUpdate() throws Exception {
JpaPagingItemReader<ImportDTO> databaseReader = new JpaPagingItemReader<>();
databaseReader.setEntityManagerFactory(entityManagerFactory);
JpaQueryProviderImpl<ImportDTO> jpaQueryProvider = new JpaQueryProviderImpl<>();
jpaQueryProvider.setQuery("ImportDTO.findAllForUpdate");
databaseReader.setQueryProvider(jpaQueryProvider);
databaseReader.setPageSize(organizationBatchSize);
// must be set to false if multi threaded
databaseReader.setSaveState(false);
databaseReader.afterPropertiesSet();
return databaseReader;
}
#Bean
public OrganizationItemWriter writer() throws Exception {
return organizationItemWriter;
}
#Bean
public StepExecutionNotificationListener stepExecutionListener() {
return new StepExecutionNotificationListener();
}
#Bean
public ChunkExecutionListener chunkListener() {
return new ChunkExecutionListener();
}
#Bean
public TaskExecutor taskExecutor() {
SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
taskExecutor.setConcurrencyLimit(maxThreadSize);
return taskExecutor;
}
#Bean
public Job importOrganizationsJob(JobCompletionNotificationListener listener) throws Exception {
return jobBuilderFactory.get("importAndUpdateOrganizationJob")
.incrementer(new RunIdIncrementer())
.listener(listener)
.start(importNewOrganizationsFromImports())
.next(updateOrganizationsFromImports())
.build();
}
#Bean
public Step importNewOrganizationsFromImports() throws Exception {
return stepBuilderFactory.get("importNewOrganizationsFromImports")
.<ImportDTO, Organization> chunk(organizationBatchSize)
.reader(findNewImportsToImport())
.processor(organizationInsertProcessor)
.writer(writer())
.taskExecutor(taskExecutor())
.listener(stepExecutionListener())
.listener(chunkListener())
.throttleLimit(organizationThreadSize)
.build();
}
#Bean
public Step updateOrganizationsFromImports() throws Exception {
return stepBuilderFactory.get("updateOrganizationsFromImports")
.<ImportDTO, Organization> chunk(organizationBatchSize)
.reader(findImportsToUpdate())
.processor(organizationUpdateProcessor)
.writer(writer())
.taskExecutor(taskExecutor())
.listener(stepExecutionListener())
.listener(chunkListener())
.throttleLimit(organizationThreadSize)
.build();
}
}
You need to add order by clause to select
I created a session bean with this code:
package ejb2;
import javax.annotation.Resource;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
#Stateless(name = "TestEJB", mappedName = "EJB2-Project1-TestEJB")
public class TestEJBBean implements TestEJB, TestEJBLocal {
#Resource
SessionContext sessionContext;
public TestEJBBean() {
}
public String getHello(String who_welcome) {
return "Hello " + who_welcome;
}
}
As you can see, it's almost a default code (except getHello method). Besides this bean I have a client:
package ejb2;
import java.util.Hashtable;
import javax.naming.CommunicationException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class TestEJBClient {
public static void main(String[] args) {
try {
final Context context = getInitialContext();
TestEJB testEJB = (TestEJB) context.lookup("EJB2-Project1-TestEJB#ejb2.TestEJB");
System.out.println(testEJB.getHello("Student"));
} catch (CommunicationException ex) {
System.out.println(ex.getClass().getName());
System.out.println(ex.getRootCause().getLocalizedMessage());
System.out.println("\n*** A CommunicationException was raised. This typically\n*** occurs when the target WebLogic server is not running.\n");
} catch (Exception ex) {
ex.printStackTrace();
}
}
private static Context getInitialContext() throws NamingException {
Hashtable env = new Hashtable();
// WebLogic Server 10.x/12.x connection details
env.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
env.put(Context.PROVIDER_URL, "t3://localhost:7101");
return new InitialContext(env);
}
}
First time it worked like a charm. But then I created another bean:
package ejb2;
import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.ejb.SessionContext;
import javax.naming.Context;
import javax.naming.InitialContext;
#Stateless(name = "ClientEJB", mappedName = "EJB2-Project1-ClientEJB")
public class ClientEJBBean implements ClientEJB, ClientEJBLocal {
#Resource
SessionContext sessionContext;
TestEJB testEJB;
public ClientEJBBean() {
try {
final Context context = new InitialContext();
testEJB = (TestEJB) context.lookup("EJB2-Project1-TestEJB#ejb2.TestEJB");
} catch (Exception ex) {
ex.printStackTrace();
}
}
public String getHelloFromBean(String who) {
return testEJB.getHello(who);
}
}
And now beans aren't working. I get an error like this:
weblogic.application.ModuleException: Unable to bind Business Interface to the JNDI name: EJB2Project1WebApp_warClientEJB_Home, throw exception javax.naming.NameAlreadyBoundException: [EJB:011224]Unable to bind the interface ejb2.ClientEJB to ClientEJB. Another EJB has already bound an interface to that name.; remaining name 'EJB2-Project1-ClientEJB#ejb2'. NestedException Message is :[EJB:011224]Unable to bind the interface ejb2.ClientEJB to ClientEJB. Another EJB has already bound an interface to that name.
What's the problem with these codes?
As far as i can see you try to deploy two stateless EJBs with the same JNDI name
Try to undeploy the current application , check the JNDI tree from Admin Console
and make sure the tree does not have the JNDI name you see as duplicate.
I have just started learning Apache Camel. I understood the basics of Routes and Components. Now I want to give a try by connecting to Oracle database, reading records from one particular table and write those records to a file using File component. To read from database I assume I need to use JDBC component and give the dataSourceName.
However, I couldn't find any info on how to create a dataSource using camel. All info that I found related to this topic uses Spring DSL examples. I don't use Spring and I just need to test this using simple standalone Java application.
I am using JDK7u25 with Apache Camel 2.12.1.
Can someone please post a sample to read from the oracle table and write to a file?
[EDIT]
After checking several solutions on the web, I came to know about following two approaches:
Camel to run as standalone. Here is my code:
import javax.sql.DataSource;
import org.apache.camel.main.Main;
import org.apache.camel.builder.RouteBuilder;
import org.apache.commons.dbcp.BasicDataSource;
public class JDBCExample {
private Main main;
public static void main(String[] args) throws Exception {
JDBCExample example = new JDBCExample();
example.boot();
}
public void boot() throws Exception {
// create a Main instance
main = new Main();
// enable hangup support so you can press ctrl + c to terminate the JVM
main.enableHangupSupport();
String url = "jdbc:oracle:thin:#MYSERVER:1521:myDB";
DataSource dataSource = setupDataSource(url);
// bind dataSource into the registery
main.bind("myDataSource", dataSource);
// add routes
main.addRouteBuilder(new MyRouteBuilder());
// run until you terminate the JVM
System.out.println("Starting Camel. Use ctrl + c to terminate the JVM.\n");
main.run();
}
class MyRouteBuilder extends RouteBuilder {
public void configure() {
String dst = "C:/Local Disk E/TestData/Destination";
from("direct:myTable")
.setBody(constant("select * from myTable"))
.to("jdbc:myDataSource")
.to("file:" + dst);
}
}
private DataSource setupDataSource(String connectURI) {
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("oracle.jdbc.driver.OracleDriver");
ds.setUsername("sa");
ds.setPassword("devon1");
ds.setUrl(connectURI);
return ds;
}
}
Using the approach mentioned by Claus lbsen. Here is the code again:
import javax.sql.DataSource;
import org.apache.camel.CamelContext;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.impl.SimpleRegistry;
import org.apache.camel.main.Main;
import org.apache.camel.builder.RouteBuilder;
import org.apache.commons.dbcp.BasicDataSource;
public class JDBCExample {
private Main main;
public static void main(String[] args) throws Exception {
String url = "jdbc:oracle:thin:#MYSERVER:1521:myDB";
DataSource dataSource = setupDataSource(url);
SimpleRegistry reg = new SimpleRegistry() ;
reg.put("myDataSource",dataSource);
CamelContext context = new DefaultCamelContext(reg);
context.addRoutes(new JDBCExample().new MyRouteBuilder());
context.start();
Thread.sleep(5000);
context.stop();
}
class MyRouteBuilder extends RouteBuilder {
public void configure() {
String dst = "C:/Local Disk E/TestData/Destination";
from("direct:myTable")
.setBody(constant("select * from myTable"))
.to("jdbc:myDataSource")
.to("file:" + dst);
}
}
private static DataSource setupDataSource(String connectURI) {
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("oracle.jdbc.driver.OracleDriver");
ds.setUsername("sa");
ds.setPassword("devon1");
ds.setUrl(connectURI);
return ds;
}
}
But in both the cases I am getting below exception:
Caused by: org.apache.camel.ResolveEndpointFailedException: Failed to resolve endpoint: jdbc://myDataSource due to: No component found with scheme: jdbc
at org.apache.camel.impl.DefaultCamelContext.getEndpoint(DefaultCamelContext.java:534)
at org.apache.camel.util.CamelContextHelper.getMandatoryEndpoint(CamelContextHelper.java:63)
at org.apache.camel.model.RouteDefinition.resolveEndpoint(RouteDefinition.java:192)
at org.apache.camel.impl.DefaultRouteContext.resolveEndpoint(DefaultRouteContext.java:106)
at org.apache.camel.impl.DefaultRouteContext.resolveEndpoint(DefaultRouteContext.java:112)
at org.apache.camel.model.SendDefinition.resolveEndpoint(SendDefinition.java:61)
at org.apache.camel.model.SendDefinition.createProcessor(SendDefinition.java:55)
at org.apache.camel.model.ProcessorDefinition.makeProcessor(ProcessorDefinition.java:500)
at org.apache.camel.model.ProcessorDefinition.addRoutes(ProcessorDefinition.java:213)
at org.apache.camel.model.RouteDefinition.addRoutes(RouteDefinition.java:909)
... 12 more
[Thread-0] INFO org.apache.camel.main.MainSupport$HangupInterceptor - Received hang up - stopping the main instance.
There is the SQL example which shows how to setup a DataSource
http://camel.apache.org/sql-example.html
Yes that examples using Spring XML. But how you setup the DataSource can be done in Java code also. Then you need to register the DataSource in the Camel Registry.
For example you can use a JndiRegistry or the SimpleRegistry. The latter is easier.
Here is some pseudo code showing the principle, of creating a registry, add your beans into this registry, and then providing the registry to the constructor of DefaultCamelContext.
SimpleRegistry registry = new SimpleRegistry();
// code to create data source here
DateSource ds = ...
registry.put("myDataSource", ds);
CamelContext camel = new DefaultCamelContext(registry);
So silly me! I had not included camel-jdbc-2.12.1.jar in the CLASSPATH. Now above examples work.
Spring was mentioned there just because it is very useful paradigm of working with DB (mainly because of templates introduced by Spring Framework.) Of course you can hook up the standard JDBC connection and implement DAO by yourself - nothing wrong with that.
I want to read from the database and write the records to a file using Camel. Below is my code:
import javax.sql.DataSource;
import org.apache.camel.CamelContext;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.impl.SimpleRegistry;
import org.apache.commons.dbcp.BasicDataSource;
public class JDBCExampleSimpleRegistry {
public static void main(String[] args) throws Exception {
final String url = "jdbc:oracle:thin:#MYSERVER:1521:myDB";
DataSource dataSource = setupDataSource(url);
SimpleRegistry reg = new SimpleRegistry() ;
reg.put("myDataSource",dataSource);
CamelContext context = new DefaultCamelContext(reg);
context.addRoutes(new JDBCExampleSimpleRegistry().new MyRouteBuilder());
context.start();
Thread.sleep(5000);
context.stop();
}
class MyRouteBuilder extends RouteBuilder {
public void configure() {
String dst = "C:/Local Disk E/TestData/Destination/?fileName=output.txt";
from("direct:myTable")
.setBody(constant("select * from myTable"))
.to("jdbc:myDataSource")
.to("file://" + dst);
}
}
private static DataSource setupDataSource(String connectURI) {
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("oracle.jdbc.driver.OracleDriver");
ds.setUsername("sa");
ds.setPassword("devon1");
ds.setUrl(connectURI);
return ds;
}
}
The above program works fine and CamelContext is elegantly shutdown. However, the target file is not created. What am I doing wrong?
I am a newbie to Apache Camel so appreciate any help. I am using JDK7 with Apache Camel 2.12.1 and is not using Spring.
You can take a look at the SQL example: http://camel.apache.org/sql-example.html and then to write to file, is just to send to a file instead of calling the bean as the sql example does.
If you want to use the JDBC component then it doesnt have built-in consumer, so you need to trigger the route using a timer or a quartz scheduler to run the route every X time.