Spring Boot + Hibernate + Oracle schema multitenancy - spring

I'm trying to get a schema-based multitenancy solution working, similar to this example but with Oracle instead of Postgres.
For example, I have three schemas: FOO, BAR, and BAZ. BAR and BAZ each have a table called MESSAGES. FOO has been granted SELECT on both BAR.MESSAGES and BAZ.MESSAGES. So if I connect as FOO and then execute
SELECT * FROM BAR.MESSAGES;
then I get a result as expected. But if I leave out the schema name (e.g. SELECT * FROM MESSAGES), then I get ORA-00942: table or view does not exist (the connection is using the wrong schema).
Here's my Dao / repository:
#Repository
public interface MessageDao extends CrudRepository<Foo, Long> {
}
The controller:
#GetMapping("/findAll")
public List<Message> findAll() {
TenantContext.setCurrentTenant("BAR");
var result = messageDao.findAll();
return result;
}
The Config:
#Configuration
public class MessageConfig {
#Bean
public JpaVendorAdapter jpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource,
MultiTenantConnectionProvider multiTenantConnectionProvider,
CurrentTenantIdentifierResolver tenantIdentifierResolver) {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan(Message.class.getPackageName());
em.setJpaVendorAdapter(this.jpaVendorAdapter());
Map<String, Object> jpaProperties = new HashMap<>();
jpaProperties.put(Environment.MULTI_TENANT, MultiTenancyStrategy.SCHEMA);
jpaProperties.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProvider);
jpaProperties.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, tenantIdentifierResolver);
jpaProperties.put(Environment.FORMAT_SQL, true);
em.setJpaPropertyMap(jpaProperties);
return em;
}
The MultitenantConnectionProvider:
#Component
public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider {
#Autowired
private DataSource dataSource;
#Override
public Connection getAnyConnection() throws SQLException {
return dataSource.getConnection();
}
#Override
public void releaseAnyConnection(Connection connection) throws SQLException {
connection.close();
}
#Override
public Connection getConnection(String currentTenantIdentifier) throws SQLException {
String tenantIdentifier = TenantContext.getCurrentTenant();
final Connection connection = getAnyConnection();
try (Statement statement = connection.createStatement()) {
statement.execute("ALTER SESSION SET CURRENT_SCHEMA = BAR");
} catch (SQLException e) {
throw new HibernateException("Problem setting schema to " + tenantIdentifier, e);
}
return connection;
}
#Override
public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
try (Statement statement = connection.createStatement()) {
statement.execute("ALTER SESSION SET CURRENT_SCHEMA = FOO");
} catch (SQLException e) {
throw new HibernateException("Problem setting schema to " + tenantIdentifier, e);
}
connection.close();
}
#SuppressWarnings("rawtypes")
#Override
public boolean isUnwrappableAs(Class unwrapType) {
return false;
}
#Override
public <T> T unwrap(Class<T> unwrapType) {
return null;
}
#Override
public boolean supportsAggressiveRelease() {
return true;
}
}
And the TenantIdentifierResolver (though not really relevant because I'm hard-coding the tenants right now in the ConnectionProviderImpl above):
#Component
public class TenantIdentifierResolver implements CurrentTenantIdentifierResolver {
#Override
public String resolveCurrentTenantIdentifier() {
String tenantId = TenantContext.getCurrentTenant();
if (tenantId != null) {
return tenantId;
}
return "BAR";
}
#Override
public boolean validateExistingCurrentSessions() {
return true;
}
}
Any ideas as to why the underlying Connection isn't switching schemas as expected?
UPDATE 1
Maybe it's something to do with the underlying Oracle connection. There is a property on OracleConnection named CONNECTION_PROPERTY_CREATE_DESCRIPTOR_USE_CURRENT_SCHEMA_FOR_SCHEMA_NAME. The documentation says:
The user also has an option to append the CURRENT_USER value to the
ADT name to obtain fully qualified name by setting this property to
true. Note that it takes a network round trip to fetch the
CURRENT_SCHEMA value.
But the problem remains even if I set this to true (-Doracle.jdbc.createDescriptorUseCurrentSchemaForSchemaName=true). This may be because the "username" on the Connection is still "FOO", even after altering the sesssion to set the schema to "BAR" (currentSchema on the Connection is "BAR"). But that would mean that the OracleConnection documentation is incorrect, wouldn't it?
UPDATE 2
I did not include the fact that we are using Spring Data JPA here as well. Maybe that has something to do with the problem?
I have found that if I include the schema name hard-coded in the entity then it works (e.g. #Table(schema="BAR")), but having a hard-coded value there is not an acceptable solution.
It might also work if we rewrite the queries as native #Query and then include {h-schema} in the SQL, but in Hibernate this is the default schema, not the 'current' (dynamic) schema, so it's not quite right either.

It turns out that setting the current tenant on the first line of the Controller like that (TenantContext.setCurrentTenant("BAR")) is "too late" (Spring has already created a transaction?). I changed the implementation to use a servlet filter to set the tenant id from a header to a request attribute, and then fetch that attribute in the TenantIdentifierResolver, instead of using the TenantContext. Now it works as it should, without any of the stuff I mentioned in the updates.

Related

Spring Boot and Spring Data with Cassandra: Continue on failed database connection

I use Spring Boot and Spring Data with Cassandra. On application startup spring establishes a connection to the database to setup the schema and initialize spring data repositories. If the database is not available, the application won't start.
I want, that the application just logs an error and starts. Of course, I can't use the repositories anymore, but other services (rest controllers etc), which are independent from the database should work. It would also be nice to see in actuator healthcheck, that cassandra is down.
For JDBC, there is a spring.datasource.continue-on-error property. I couldn't find something similar for Cassandra.
I also tried to create a custom cassandra configuration and trying to catch Exception on CqlSession creation, but I couldn't achieve the desired behavior.
EDIT: As suggested by #adutra, I tried to set advanced.reconnect-on-init, Application tries to establish the connection, but the application is not fully initialized (e.g. REST controller are not reachable)
#Configuration
public class CustomCassandraConfiguration extends CassandraAutoConfiguration {
#Bean
public DriverConfigLoaderBuilderCustomizer driverConfigLoaderBuilderCustomizer() {
return builder -> builder.withBoolean(DefaultDriverOption.RECONNECT_ON_INIT, true);
}
}
EDIT2: I have now working example (application starts, custom health check for cassandra), but if feels pretty ugly:
CustomCassandraAutoConfiguration
#Configuration
public class CustomCassandraAutoConfiguration extends CassandraAutoConfiguration {
private final Logger logger = LoggerFactory.getLogger(getClass());
#Override
#Bean
public CqlSession cassandraSession(CqlSessionBuilder cqlSessionBuilder) {
try {
return super.cassandraSession(cqlSessionBuilder);
} catch (AllNodesFailedException e) {
logger.error("Failed to establish the database connection", e);
}
return new DatabaseNotConnectedFakeCqlSession();
}
#Bean
public CassandraReactiveHealthIndicator cassandraHealthIndicator(ReactiveCassandraOperations r, CqlSession session) {
if (session instanceof DatabaseNotConnectedFakeCqlSession) {
return new CassandraReactiveHealthIndicator(r) {
#Override
protected Mono<Health> doHealthCheck(Health.Builder builder) {
return Mono.just(builder.down().withDetail("connection", "was not available on startup").build());
}
};
}
return new CassandraReactiveHealthIndicator(r);
}
}
CustomCassandraDataAutoConfiguration
#Configuration
public class CustomCassandraDataAutoConfiguration extends CassandraDataAutoConfiguration {
public CustomCassandraDataAutoConfiguration(CqlSession session) {
super(session);
}
#Bean
public SessionFactoryFactoryBean cassandraSessionFactory(CqlSession session, Environment environment, CassandraConverter converter) {
SessionFactoryFactoryBean sessionFactoryFactoryBean = super.cassandraSessionFactory(environment, converter);
// Disable schema action if database is not available
if (session instanceof DatabaseNotConnectedFakeCqlSession) {
sessionFactoryFactoryBean.setSchemaAction(SchemaAction.NONE);
}
return sessionFactoryFactoryBean;
}
}
DatabaseNotConnectedFakeCqlSession (Fake session implementation)
public class DatabaseNotConnectedFakeCqlSession implements CqlSession {
#Override
public String getName() {
return null;
}
#Override
public Metadata getMetadata() {
return null;
}
#Override
public boolean isSchemaMetadataEnabled() {
return false;
}
#Override
public CompletionStage<Metadata> setSchemaMetadataEnabled( Boolean newValue) {
return null;
}
#Override
public CompletionStage<Metadata> refreshSchemaAsync() {
return null;
}
#Override
public CompletionStage<Boolean> checkSchemaAgreementAsync() {
return null;
}
#Override
public DriverContext getContext() {
return new DefaultDriverContext(new DefaultDriverConfigLoader(), ProgrammaticArguments.builder().build());
}
#Override
public Optional<CqlIdentifier> getKeyspace() {
return Optional.empty();
}
#Override
public Optional<Metrics> getMetrics() {
return Optional.empty();
}
#Override
public <RequestT extends Request, ResultT> ResultT execute( RequestT request, GenericType<ResultT> resultType) {
return null;
}
#Override
public CompletionStage<Void> closeFuture() {
return null;
}
#Override
public CompletionStage<Void> closeAsync() {
return null;
}
#Override
public CompletionStage<Void> forceCloseAsync() {
return null;
}
#Override
public Metadata refreshSchema() {
return null;
}
}
Any suggestions?
You can set the option datastax-java-driver.advanced.reconnect-on-init to true to achieve the effect you want. Its usage is explained in the configuration reference page in the driver docs:
Whether to schedule reconnection attempts if all contact points are unreachable on the first initialization attempt.
If this is true, the driver will retry according to the reconnection policy. The SessionBuilder.build() call - or the future returned by SessionBuilder.buildAsync() - won't complete until a contact point has been reached. If this is false and no contact points are available, the driver will fail with an AllNodesFailedException.
However be careful: with this option set to true, as stated above, any component trying to access a CqlSession bean, even if the session bean is lazy, will block until the driver is able to connect, and might block forever if the contact points are wrong.
If that's not acceptable for you, I would suggest that you wrap the CqlSession bean in another bean that will check if the future returned by SessionBuilder.buildAsync() is done or not, and either block, throw or return null, depending on the caller's expectations.
[EDIT] I've reached out internally to the DataStax Drivers team last night and adutra has responded so I'm withdrawing my response.

How to mock bean HikariDataSource correctly?

I wrote integration test using Mockito, but it works when connection to database was set. Actually test just check possibility access some endpoints and not related to the data access layer. So I don't need database for it yet.
Reason of failing test when database is down - HikariDatasource check connection to the database when spring instantiates context. Mocking doesn't return Connection and it lead to the fail of application. Solution that i have found is use hsql in memory database, but for me it looks like work around. Probably exists other solution providing some fake data?
Not sure that this is elegant solution, but I need to force work tests like this
mockMvc.perform(
post("/some").contentType(MediaType.APPLICATION_JSON_UTF8)
.content(objectMapper.writeValueAsString(someDto))
.header(HttpHeaders.AUTHORIZATION, AUTH_HEADER)
.accept(MediaType.APPLICATION_JSON_UTF8)
).andExpect(status().is(201));
After debugging and searching I have found solution that allowed start container without database in memory.
#TestConfiguration
#ComponentScan(basePackages = "com.test")
#ActiveProfiles("test")
public class TestConfig {
//Other Beans
#Bean
public DataSource getDatasource() {
return new MockDataSource();
}
}
class MockDataSource implements DataSource {
#Override
public Connection getConnection() throws SQLException {
return createMockConnection();
}
#Override
public Connection getConnection(String username, String password) throws SQLException {
return getConnection();
}
#Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
#Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
#Override
public void setLoginTimeout(int seconds) throws SQLException {
}
#Override
public int getLoginTimeout() throws SQLException {
return 0;
}
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
#Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
#Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
public static Connection createMockConnection() throws SQLException {
// Setup mock connection
final Connection mockConnection = mock(Connection.class);
// Autocommit is always true by default
when(mockConnection.getAutoCommit()).thenReturn(true);
// Handle Connection.createStatement()
Statement statement = mock(Statement.class);
when(mockConnection.createStatement()).thenReturn(statement);
when(mockConnection.createStatement(anyInt(), anyInt())).thenReturn(statement);
when(mockConnection.createStatement(anyInt(), anyInt(), anyInt())).thenReturn(statement);
when(mockConnection.isValid(anyInt())).thenReturn(true);
// Handle Connection.prepareStatement()
PreparedStatement mockPreparedStatement = mock(PreparedStatement.class);
when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement);
when(mockConnection.prepareStatement(anyString(), anyInt())).thenReturn(mockPreparedStatement);
when(mockConnection.prepareStatement(anyString(), (int[]) any())).thenReturn(mockPreparedStatement);
when(mockConnection.prepareStatement(anyString(), (String[]) any())).thenReturn(mockPreparedStatement);
when(mockConnection.prepareStatement(anyString(), anyInt(), anyInt())).thenReturn(mockPreparedStatement);
when(mockConnection.prepareStatement(anyString(), anyInt(), anyInt(), anyInt())).thenReturn(mockPreparedStatement);
doAnswer((Answer<Void>) invocation -> null).doNothing().when(mockPreparedStatement).setInt(anyInt(), anyInt());
ResultSet mockResultSet = mock(ResultSet.class);
when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet);
when(mockResultSet.getString(anyInt())).thenReturn("aString");
when(mockResultSet.next()).thenReturn(true);
// Handle Connection.prepareCall()
CallableStatement mockCallableStatement = mock(CallableStatement.class);
when(mockConnection.prepareCall(anyString())).thenReturn(mockCallableStatement);
when(mockConnection.prepareCall(anyString(), anyInt(), anyInt())).thenReturn(mockCallableStatement);
when(mockConnection.prepareCall(anyString(), anyInt(), anyInt(), anyInt())).thenReturn(mockCallableStatement);
ResultSet mockResultSetTypeInfo = mock(ResultSet.class);
DatabaseMetaData mockDataBaseMetadata = mock(DatabaseMetaData.class);
when(mockDataBaseMetadata.getDatabaseProductName()).thenReturn("PostgreSQL");
when(mockDataBaseMetadata.getDatabaseMajorVersion()).thenReturn(8);
when(mockDataBaseMetadata.getDatabaseMinorVersion()).thenReturn(2);
when(mockDataBaseMetadata.getConnection()).thenReturn(mockConnection);
when(mockDataBaseMetadata.getTypeInfo()).thenReturn(mockResultSetTypeInfo);
when(mockConnection.getMetaData()).thenReturn(mockDataBaseMetadata);
// Handle Connection.close()
doAnswer((Answer<Void>) invocation -> null).doThrow(new SQLException("Connection is already closed")).when(mockConnection).close();
// Handle Connection.commit()
doAnswer((Answer<Void>) invocation -> null).doThrow(new SQLException("Transaction already committed")).when(mockConnection).commit();
// Handle Connection.rollback()
doAnswer((Answer<Void>) invocation -> null).doThrow(new SQLException("Transaction already rolled back")).when(mockConnection).rollback();
return mockConnection;
}
}
Mocking DataSource allows start container and provide post call to the controller using MockMvc.

can we connect with two DataBase Schemas with spring data jpa using same repository , model

I want to connect to two Database schemas which are on same database server , for that I want to use only one set of models and JPA repositories , with two data soources connecting to two different schemas .But as of now i am not able to find a way where we can reuse the existing models and repositories , for now I have created two sets of models and repositories having different schemas .Is there a way we can reuse the models and Repositories ?
But as of now i am not able to find a way where we can reuse the existing models and repositories , for now I have created two sets of models and repositories having different schemas
NOTE:
i am able to connect to two schemas via two data sources hence multi-tenancy support is not needed the only thing is while connecting to two separate schemas i am creating models(enitities) and JPA repositories twices even though tables are identical in both the schemas , is there are a to remove code duplicacy
looks like you need multi-tenancy support. Tenant per schema
You need TenantInterceptor which resolves tenantId e.g. from session or JWT token. And MultiTenantConnectionProvider which returns desired provider.
Some code from Multi-Tenancy Implementation for Spring Boot + Hibernate Projects
#Component
public class TenantInterceptor extends HandlerInterceptorAdapter {
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Value("${jwt.header}")
private String tokenHeader;
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String authToken = request.getHeader(this.tokenHeader);
String tenantId = jwtTokenUtil.getTenantIdFromToken(authToken);
TenantContext.setCurrentTenant(tenantId);
return true;
}
#Override
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
TenantContext.clear();
}
}
and
#Component
public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider {
#Autowired
private DataSource dataSource;
#Override
public Connection getAnyConnection() throws SQLException {
return dataSource.getConnection();
}
#Override
public void releaseAnyConnection(Connection connection) throws SQLException {
connection.close();
}
#Override
public Connection getConnection(String tenantIdentifie) throws SQLException {
String tenantIdentifier = TenantContext.getCurrentTenant();
final Connection connection = getAnyConnection();
try {
if (tenantIdentifier != null) {
connection.createStatement().execute("USE " + tenantIdentifier);
} else {
connection.createStatement().execute("USE " + DEFAULT_TENANT_ID);
}
}
catch ( SQLException e ) {
throw new HibernateException(
"Problem setting schema to " + tenantIdentifier,
e
);
}
return connection;
}
#Override
public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
try {
connection.createStatement().execute( "USE " + DEFAULT_TENANT_ID );
}
catch ( SQLException e ) {
throw new HibernateException(
"Problem setting schema to " + tenantIdentifier,
e
);
}
connection.close();
}
#SuppressWarnings("rawtypes")
#Override
public boolean isUnwrappableAs(Class unwrapType) {
return false;
}
#Override
public <T> T unwrap(Class<T> unwrapType) {
return null;
}
#Override
public boolean supportsAggressiveRelease() {
return true;
}
}

Change Multitenancy sessions manually

I need to create a multitenanacy application with ability to switch between schemas inside my java-code (not based on a user request).
I've read articles:
https://fizzylogic.nl/2016/01/24/make-your-spring-boot-application-multi-tenant-aware-in-2-steps/
http://www.greggbolinger.com/tenant-per-schema-with-spring-boot/
Solution works fine, when the schema is passed in Rest-request.
However I need to implement the following logic:
public void compare(String originalSchema, String secondSchema){
TenantContext.setCurrentTenant(originalSchema);
List<MyObject> originalData = myRepository.findData();
TenantContext.setCurrentTenant(secondSchema);
List<MyObject> migratedData = myRepository.findData();
}
The point is, that connection is not switched, when I manually set up TenenantContext. MultiTenantConnectionProviderImpl.getConnection is invoked only on the first call to my repository.
#Component
public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider {
#Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
final Connection connection = getAnyConnection();
try {
connection.createStatement().execute( "ALTER SESSION SET CURRENT_SCHEMA = " + tenantIdentifier );
}
catch ( SQLException e ) {
throw new HibernateException(
"Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]",e);
}
return connection;
}
}
Is it possible to force switching sessions?
Found a hard-coded solution.
#Service
public class DatabaseSessionManager {
#PersistenceUnit
private EntityManagerFactory entityManagerFactory;
public void bindSession() {
if (!TransactionSynchronizationManager.hasResource(entityManagerFactory)) {
EntityManager entityManager = entityManagerFactory.createEntityManager();
TransactionSynchronizationManager.bindResource(entityManagerFactory, new EntityManagerHolder(entityManager));
}
}
public void unbindSession() {
EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager
.unbindResource(entityManagerFactory);
EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
}
}
Each block, loading data in a new tenantContext should execute the following:
databaseSessionManager.unbindSession();
TenantContext.setCurrentTenant(schema);
databaseSessionManager.bindSession();
//execute selects
Well, you need it
public interface Service {
List<MyObject> myObjects();
}
#Service
#Transactional(propagation = Propagation.REQUIRES_NEW)
public class ServiceImpl implements Service {
#Autowired
private MyRepository myRepository;
#Override
public List<MyObject> myObjects() {
return myRepository.findData();
}
}
#Service
public class AnotherService() {
#Autowired
private Service service;
public void compare(String originalSchema, String secondSchema){
TenantContext.setCurrentTenant(originalSchema);
List<MyObject> originalData = service.myObjects();
TenantContext.setCurrentTenant(secondSchema);
List<MyObject> migratedData = service.myObjects();
}
}
Try using
spring.jpa.open-in-view=false
in your application.properties file.
More info on this
What is this spring.jpa.open-in-view=true property in Spring Boot?
Hope this helps..

stored procedure isn't get executed

I really need your help. I have a JDBC code that calls an oracle stored procedure. This is the class that has the code:
public class DAOImpl {
private String sql = "{call MAIN.SP_CALC(?,?,?,?,?)}";
private Connection conn = null;
public DAOImpl(Connection conn) {
this.conn = conn;
}
#Override
public synchronized void executeSP(String year, String month, Long id) throws SQLException {
try (CallableStatement cs = conn.prepareCall(sql);) {
cs.setObject(1, id);
cs.setObject(2, year);
cs.setObject(3, month);
cs.setObject(4, 0);
cs.registerOutParameter(5, Types.INTEGER);
cs.execute();
}
}
}
I have two clients for this class, a JavaSE Tester class and a stateless EJB. The JavaSE main() method have this code:
try {
DriverManager.registerDriver(new OracleDriver());
Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
DAOImpl dao = new DAOImpl(conn);
dao.executeSP("2015", "02", 99561010l);
} catch (SQLException ex) {
Logger.getLogger(SPTester.class.getName()).log(Level.SEVERE, null, ex);
}
The stateless EJB has this other code:
try {
conn = ds.getConnection();
DAO dao = DAOImpl(conn);
dao.executeSP("2015", "02", 99561010l);
} catch (SQLException ex) {
throw new SQLException(ex);
} finally {
conn.close();
}
ds is an instance variable injected to the EJB as a DataSource that comes from a JDBC Connection Pool declared in glassfish of type oracle.jdbc.xa.client.OracleXADataSource. The variables DB_URL, USER, PASS of the JavaSE programm, have the same values that use the JDBC Connection Pool
As you'll notice, the only difference is the Connection object. Both JavaSE and EJB use the ojdbc6.jar driver. Both uses java 1.7.0_u2, that's why I use try-with-resources.
The problem is that only the JavaSE works! Both of them returns after some seconds of processing but only the JavaSE programm works. I have tried many things.. CMT and BMT EJB, wrapped and unwrapped types of id, synchronize and unsynchronize method, etc. I need this code working in the EJB :(
What's the wrong with my EJB?
thanks in advance
This is the CMT version of the EJB
#Stateless
#LocalBean
#Interceptors({PropagateSessionContextInterceptor.class})
public class CalcBean extends BaseContextSessionBean {
#Resource(mappedName = "jdbc/maestro")
protected DataSource ds;
public Connection conn = null;
public void executeSP(String year, String month, Long id) throws SQLException {
try {
conn = ds.getConnection();
DAO dao = DAOImpl(conn);
dao.executeSP("2015", "02", 99561010l);
} catch (SQLException ex) {
throw new SQLException(ex);
} finally {
conn.close();
}
}
}
This is the BMT version of the EJB
#Stateless
#LocalBean
#Interceptors({PropagateSessionContextInterceptor.class})
#TransactionManagement(TransactionManagementType.BEAN)
public class CalcBean extends BaseContextSessionBean {
#Resource(mappedName = "jdbc/maestro")
protected DataSource ds;
public Connection conn = null;
#Resource
UserTransaction tx;
public void executeSP(String year, String month, Long id) throws SQLException {
try {
try {
tx.begin();
conn = ds.getConnection();
DAO dao = DAOImpl(conn);
dao.executeSP("2015", "02", 99561010l);
tx.commit();
} catch (SQLException ex) {
throw new SQLException(ex);
} finally {
conn.close();
}
} catch (Exception e) {
throw new EJBException(e);
}
}
}
public class BaseContextSessionBean {
#Resource
public SessionContext ctx;
}
public class PropagateSessionContextInterceptor {
#AroundInvoke
public Object myInterceptor(InvocationContext ctx) throws Exception
{
BaseContextSessionBean sb = (BaseContextSessionBean)ctx.getTarget();
ControlListener.setSessionContext(sb.ctx);
Object o = ctx.proceed();
return o;
}
}
public interface DAO {
public void executeSP(String year, String month, Long id) throws SQLException;
}
I don't know the reason of BaseContextSessionBean and PropagateSessionContextInterceptor. Originally, the EJB used a useless JDBC framework (that uses those classes), so I replaced it with the DAOImpl class. I use a DAO interface to decouple the implementation, the DaoImpl is been execute (in the EJB, the DAOImpl implements DAO, I forgot to put that in the earlier post)

Resources