Create SqlSession from existing Connection directly - java

I'm working with existing Java code wherein there is an existing JDBC connection pooling mechanism on deployed systems and an already existing setup to get JDBC connections. I'd like to leverage this to create a MyBatis SqlSession object without creating a Configuration, DataSource, and other things
I have code that already creates a java.sql.Connection object is given the desired resource. I'd like to leverage this and get that SqlSession object and use MyBatis from that point onwards.
I don't want MyBatis to manage connection pooling, determining which data source to use, etc. Is this possible?

I'm afraid you can't avoid creation of the Configuration object. It is used by the internal mybatis machinery like executors. But even if you could it will not help you much. In this case you will need to implement most of Configuration functionality yourself so it does not make sense to do that.
You main goal is to be able to use SqlSessionFactory.openSession(Connection connection) method. To do this you need to have SqlSessionFactory. The easiest way for you is to create Configuration like it is descried in mybatis documentation:
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
// set properties to configuration here
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(configuration);
Now if your connection pool does implement DataSource you use it directly to create environment. If it does not you need to create an adapter around your pool which implements javax.sql.DataSource.

My solution is similar to Roman's above, but I needed to create an Oracle datasource. For various reasons, the connection needs to be created using the Class.forName type sequence
Class.forName("oracle.jdbc.driver.OracleDriver");
String connectionString = "jdbc:oracle:thin:#//yadayada";
String username = "myusername";
String password = "mypassword";
OracleDataSource oracleDataSource = new OracleDataSource();
oracleDataSource.setURL(connectionString);
oracleDataSource.setPassword(password);
oracleDataSource.setUser(username);
environment = new Environment("dev",transactionFactory,oracleDataSource);
configuration = new Configuration(environment);
configuration.addMappers("MyMybatisMapper");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
return sqlSessionFactory.openSession();
What I was missing was the OracleDataSource object.

Related

Set jdbc interceptor using DriverManager class

I am using DriverManager.getConnection(url, prop) to get the connection. I am trying to inject the jdbc interceptors using properties like below but it is not working.
Properties prop = new Properties();
...
prop.setProperty("jdbcInterceptors", "com.amazonaws.xray.sql.mysql.TracingInterceptor;");
However, when we try to do via datasource it is working.
import org.apache.tomcat.jdbc.pool.DataSource;
DataSource source = new DataSource();
source.setUrl("url");
source.setUsername("user");
source.setPassword("password");
source.setDriverClassName("com.mysql.jdbc.Driver");
source.setJdbcInterceptors("com.amazonaws.xray.sql.mysql.TracingInterceptor;");
Not sure what is wrong with DriverManager properties.
These interceptors are a feature of the Tomcat org.apache.tomcat.jdbc.pool.DataSourceProxy and its subclass org.apache.tomcat.jdbc.pool.DataSource. This is not a feature of JDBC itself, nor a feature of the JDBC driver you're using, so the only way to access it is through a Tomcat data source.
In short, it doesn't work with DriverManager because this feature doesn't exist in DriverManager.

How to set the jdbc ClientInfo in a MySQL DataSource

In Java with MySQL we want to add the jdbc ClientInfo to identify the source of each query. Currently we can do something like:
try(Connection connection = dataSource.getConnection()){
connection.setClientInfo("ApplicationName", "MyApp");
}
But I need to add it to every connection created and means checking all the source code for places where a new connection is created. I will like to set it to the DataSource level.
So far what works for me is to extends the DataSource with a custom overriden getConnection method that calls setClientInfo. This is not only a dirty workarround but datasource specific.
I have seen that mysql driver has ClientInfoProviders like the default com.mysql.cj.jdbc.CommentClientInfoProvider. A custom ClientInfoProvider can be configured like:
Properties properties = new Properties();
properties.setProperty(PropertyKey.clientInfoProvider.getKeyName(), "foo.bar.CustomClientInfoProvider");
properties.setProperty(APPLICATION_NAME, "MyApp");
HikariConfig dataSourceConfig = new HikariConfig();
dataSourceConfig.setDataSourceProperties(properties);
...
But it is only called if someone calls the getClientInfo in the connection anyway.
So I will like to know:
Is there support in the MySql driver to set the clientInfo in the DataSource just by setting properties?
If there is a way. How can it be done?
I think you can use AspectJ as a possible solution for it. You can create an aspect which will intercept calls of the DataSource.getConnection method and then call the setClientInfo method with configured parameters when the connection is established.

javax.naming.NoInitialContextException with QueryDSL

I am trying to write a simple query using QueryDSL, but my attempt fails with the following exception.
Caused by: javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initial
I tried executing query the following ways.
SessionFactory sf = new Configuration().configure().buildSessionFactory();
Session session = sf.openSession();
JPQLQuery query = new HibernateQuery(session);
QClient t = QClient.client;
List<Client> lst = query.from(t).list(t);
System.out.println(lst.size());
And another way.
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("my.package.entities");
EntityManager em = emf.createEntityManager();
JPAQuery query = new JPAQuery(em);
QClient t = QClient.client;
List<Client> lst = query.from(t).list(t);
System.out.println(lst.size());
As stated, both this ways failed with the same exception.
I am using Postrges DB, and the parameters are specified in hibernate.cfg.xml.
Do I need to setup something more for this to work?
The javax.naming package comprises the JNDI API. Since it's just an API, rather than an implementation, you need to tell it which implementation of JNDI to use. The implementations are typically specific to the server you're trying to talk to.
To specify an implementation, you pass in a Properties object when you construct the InitialContext. These properties specify the implementation to use, as well as the location of the server. The default InitialContext constructor is only useful when there are system properties present, but the properties are the same as if you passed them in manually.
As to which properties you need to set, that depends on your server. You need to hunt those settings down and plug them in.
Sample:
Properties properties = new Properties();
properties.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
properties.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces");
properties.put("jnp.socket.Factory", "org.jnp.interfaces.TimedSocketFactory");
properties.setProperty("java.naming.provider.url", "jnp://" + remoteHost + :1099");
context = new InitialContext(properties);
or
look for this
Issue

Dynamically routed sqlSessionFactory in mybatis

Is it possible to choose Mapper to execute in Mybatis?
I am using dynamically routed datasource and give it as parameter to sqlSessionFactory - it is working.
The problem is, however, that different databases may require other SQL queries (very small differences, but differences).
Is it possible to create dynamically routed sqlSessionFactory - and give to them other package mappers?
This is how you obtain a session factory:
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
A couple of things are declared in in mybatis-config.xml, amongst whom are the datasources and the mappers.
Then create another Mybatis config file (with its own datesource and mappers) and dynamically choose the file to build the session factory. You can still provide the connection when opening the session, although I recommend using the implicit one provided by the factory because it will match with the mapper. (Unless this is a complex application where you need to reuse the connection, e.g: to complete a transaction)

Inject java.sql.Connection into controller for Ninja Framework

I want to use JOOQ to access my database from the Ninja Framework. How can I get a JDBC connection from a controller?
Here's resources I found that didn't quite work.
How to retrieve the datasource used by a persistence unit programmatically - Tedious set of steps to get the connection from an EntityManager.
http://blog.jooq.org/2015/05/26/type-safe-queries-for-jpas-native-query-api/ - works by building a query in JOOQ and passing to EntityManager.createNativeQuery. It's functional, but it's not as nice as just having the connection.
Could I inject the connection into a controller like so:
public Result myController(#DBConnection Connection connection) {
List<String> articles = DSL.using(connection).selectFrom(ARTICLE).fetch(ARTICLE.TITLE);
return Results.html().render("template", articles);
}
DropWizards has a plugin that looks like a winner: https://github.com/benjamin-bader/droptools/tree/master/dropwizard-jooq
public BlogPost getPost(#QueryParam("id") int postId, #Context DSLContext database) {
BlogPostRecord post = database
.selectFrom(POST)
.where(POST.ID.equal(postId))
.fetchOne();
// do stuff
}
Following up on #LukasEder's answer this is the approach:
HibernateEntityManagerFactory hibernateEntityManagerFactory = ((EntityManagerImpl) entityManager).getFactory();
SessionFactoryImpl sessionFactoryImpl = (SessionFactoryImpl) hibernateEntityManagerFactory.getSessionFactory();
C3P0ConnectionProvider c3P0ConnectionProvider = (C3P0ConnectionProvider) sessionFactoryImpl.getConnectionProvider();
Connection connection = c3P0ConnectionProvider.getConnection();
This is obviously very very strange and bad code.
A clean solution is to provide access to Connection / DataSource by Ninja directly (separating the connection pool from Hibernate or any implementation). That is not too hard and is partly done in the ebeans plugin. Let's discuss that on our mailing list if you are interested in contributing code :)
Short of any option to retrieve a JDBC Connection or DataSource from ninja framework directly, the standard approach should be to "unwrap" it from the EntityManager:
Connection connection = em.unwrap(Connection.class);
See also: How to get DataSource or Connection from JPA2 EntityManager in Java EE 6
A Hibernate-specific approach is documented here: How to retrieve the datasource used by a persistence unit programmatically

Categories