I'm trying to build a simple mailer daemon for a Tomcat 7, MySQL app (and Eclipse). This the first time I have tried to use a ServletContextListener.
Everything works perfectly. Except, if I change my mailer code, and Tomcat reloads the class. It then bombs with a JNDI exception where it can't find the database. I'm not comfortable using it as-is. I don't want a class reload to kill the task on the server.
Everything works fine after a restart and before the reload. So I must be missing something or doing things in the wrong order.
The database connection is done in the DAO. So after restarting, the DAO must be getting severed?
Any help would be most appreciated...
The error I am getting is:
Name [comp/env/jdbc/somedb] is not bound in this Context. Unable to find [comp].
javax.naming.NameNotFoundException: Name [comp/env/jdbc/somedb] is not bound in this Context. Unable to find [comp].
PooledConnection has already been closed.
at org.apache.naming.NamingContext.lookup(NamingContext.java:819)
at org.apache.naming.NamingContext.lookup(NamingContext.java:167)
at org.apache.naming.SelectorContext.lookup(SelectorContext.java:156)
at javax.naming.InitialContext.lookup(Unknown Source)
at util.DbUtil.getConnection(DbUtil.java:23)
at dao.NoticeDao.getNoticesByEvent(NoticeDao.java:49)
at dao.NoticeDao.getNoticesByStatus(NoticeDao.java:46)
at util.AppMailer.sendMailQueue(AppMailer.java:88)
at util.AppMailer.run(AppMailer.java:71)
at java.lang.Thread.run(Unknown Source)
java.sql.SQLException: PooledConnection has already been closed.
at org.apache.tomcat.jdbc.pool.DisposableConnectionFacade.invoke(DisposableConnectionFacade.java:86)
at com.sun.proxy.$Proxy7.prepareStatement(Unknown Source)
at dao.NoticeDao.getNoticesByEvent(NoticeDao.java:60)
at dao.NoticeDao.getNoticesByStatus(NoticeDao.java:46)
at util.AppMailer.sendMailQueue(AppMailer.java:88)
at util.AppMailer.run(AppMailer.java:71)
at java.lang.Thread.run(Unknown Source)
Update: For a second attempt, I simplified and separated the daemon stuff from the application logic. The app logic is now fully standalone. But I have the same problem.
public class AppMailerRunner implements ServletContextListener {
private ServletContext context = null;
private Thread mailerThread;
public AppMailerRunner() {}
#Override
public void contextInitialized(ServletContextEvent event) {
this.context = event.getServletContext();
System.out.printf("Starting: %s\n",this.getClass());
mailerThread = new Thread(new MailerDaemon());
mailerThread.setDaemon(true);
mailerThread.start();
}
#Override
public void contextDestroyed(ServletContextEvent event) {
System.out.printf("Stopping: %s\n",this.getClass());
mailerThread.interrupt();
this.context = null;
}
class MailerDaemon implements Runnable {
#Override
public void run() {
AppMailer appMailer = new AppMailer();
while(!Thread.currentThread().isInterrupted()){
try {
appMailer.sendMailQueue();
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
...
<listener>
<listener-class>util.AppMailerRunner</listener-class>
</listener>
...
public class AppMailer{
private NoticeDao noticeDao;
private Session mailSession;
private Boolean sending;
...
public AppMailer() {
super();
noticeDao = new NoticeDao();
sending = false;
}
do stuff...
...
public class NoticeDao {
public NoticeDao() {
}
...
public List<Notice> getNotices() {
Connection conn = DbUtil.getConnection();
List<Notice> notices = new ArrayList<Notice>();
try {
PreparedStatement ps = conn.prepareStatement("SELECT * FROM notices");
ResultSet rs = ps.executeQuery();
while (rs.next()) {
Notice notice = mapFields(rs);
notices.add(notice);
}
} catch (SQLException e) {
System.out.println(e.getMessage());
e.printStackTrace();
} finally {
DbUtil.close(conn);
}
return notices;
}
private static Notice mapFields(ResultSet rs) throws SQLException {
Notice notice = new Notice();
notice.setId( rs.getLong("id"));
notice.setItemid( rs.getLong("itemid"));
notice.setItemtype( rs.getString("itemtype"));
notice.setTestmode( rs.getBoolean("testmode"));
notice.setName( rs.getString("name"));
notice.setStatus( rs.getString("status"));
notice.setError( rs.getString("error"));
notice.setCreated( rs.getDate("created"));
notice.setModified( rs.getDate("modified"));
notice.setLog( rs.getString("log"));
return notice;
}
...
}
...
public class DbUtil {
private static Connection conn = null;
public DbUtil() {
}
public static Connection getConnection() {
InitialContext ctx;
try {
ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup("java:comp/env/jdbc/somedb");
conn = ds.getConnection();
} catch (NamingException e) {
System.out.println(e.getMessage());
e.printStackTrace();
} catch (SQLException e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
return conn;
}
public static void close(Connection conn){
if(conn!=null)
try {
conn.close();
} catch (SQLException e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
}
Try adding break statement in the catch block in your MailerDaemon class.
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
Note that interruption status is cleared when InterruptedException is thrown. So the thread created in the contextInitialized will never break out of the loop.
See the javadoc here.
Hope this helps.
Related
I've got problem with connection to hsqldb using jdbc in java app. After parsing in main class from json to java I get 3 objects in list which i am trying to save to database.
Here is Dao class
public class EventDao {
private static final String URL = "jdbc:hsql:file:C:/Applications/appName//APPFOLDER";
private static final String USER = "sa";
private static final String PASS = "";
private Connection connection;
public EventDao() {
try {
Class.forName("org.hsqldb.jdbcDrive");
connection = DriverManager.getConnection(URL, USER, PASS);
} catch (ClassNotFoundException e) {
System.out.println("Couldnot establish connection");
} catch (SQLException e) {
e.printStackTrace();
}
}
public void save(Event event) {
final String sql = "insert into event(id,state,timestamp,type,host,alert) values (?,?,?,?,?,?)";
try {
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, event.getId());
preparedStatement.setString(2, event.getState());
preparedStatement.setString(3, event.getTimestamp().toString());
preparedStatement.setString(4, event.getType());
preparedStatement.setString(5, event.getHost());
preparedStatement.setString(6, event.getAlert().toString());
preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
public void close() {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
And here is main when i am trying to save object Event to db (the list have 3 objects)
public static void main(String[] args) {
EventParser eventParser = new EventParser();
eventParser.mainLoop();
}
public void mainLoop() {
try {
EventDao eventDao = new EventDao();
Map<String, EventWrapper> eventsFromFile = readEventsFromFile();
List<Event> eventsToSave = calculateEventTime(eventsFromFile);
for (Event event : eventsToSave) {
eventDao.save(event);
}
eventDao.close();
System.out.println(eventsFromFile);
} catch (IOException e) {
e.printStackTrace();
}
}
After debuging i found out that connection is null. Any ideas why?
The correct form of the URL is:
URL = "jdbc:hsqldb:file:C:/Applications/appName/APPFOLDER";
The Class.forName("org.hsqldb.jdbcDrive") ensures the HSQLDB jar is on your classpath and loads the JDBC driver class form the jar. This was done without error. The error message indicates there is no driver available for the incorrect jdbc:hsql:file url.
I am building a basic java application to load some files into a mysql database. I am able to load the files up and populate my tables without any problems. However after speaking to someone who reviewed my code, I am apparently not correctly closing my connections and wasting resources. Where am I not closing up the connections? Have I done this incorrectly?
I am using the try-with-resources construct within my DbSinger class to execute prepared statements to my database, which should automatically close the connection so long as the AutoCloseable interface is implemented, which it is in the parent class of Db. The close() method however is never reached. The DbSinger is instantiated inside my main() and then runs it's single method populateSingers() with an ArrayList of Singer objects.
Connection Class
public class SQLConnection {
private static final String servername = "localhost";
private static final int port = 3306;
private static final String user = "ng_user";
private static final String pass = "ng";
private static final String db = "ng_music";
private static final String connectionString = "jdbc:mysql://" + servername + ":" + port + "/" + db;
public Connection provide() {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
return DriverManager.getConnection(connectionString, user, pass);
}
catch (SQLException | ClassNotFoundException e) {
throw new SQLConnectionException(e);
}
}
public class SQLConnectionException extends RuntimeException {
SQLConnectionException(Exception e) {super(e);}
}
}
Abstract parent class
public abstract class Db implements AutoCloseable{
private Connection connection;
Db() {
SQLConnection sqlC = new SQLConnection();
this.connection = sqlC.provide();
}
#Override
public synchronized void close() throws SQLException {
if(connection != null) {
connection.close();
connection = null;
System.out.println("Connection closed");
}
}
Connection getConnection() {
return connection;
}
boolean checkIfPopulated(String query){
try {
PreparedStatement ps = getConnection().prepareStatement(query);
ResultSet rs = ps.executeQuery();
return !rs.next();
} catch (SQLException e) {
e.printStackTrace();
}
return true;
}
}
Concrete class to execute queries to database for singers table
public class DbSinger extends Db {
public DbSinger() {
super();
}
public void populateSingers(ArrayList<Singer> singers) {
String populateSingersQuery = "insert into ng_singers(name, dob, sex) values(?,?,?)";
if(!checkIfPopulated("select * from ng_singers")){
System.out.println("Singer Table is already populated");
return;
}
try (PreparedStatement ps = getConnection().prepareStatement(populateSingersQuery)) {
for (Singer s : singers) {
ps.setString(1, s.getName());
ps.setDate(2, java.sql.Date.valueOf(s.getDob()));
ps.setString(3, s.getSex());
ps.addBatch();
}
ps.executeBatch();
System.out.println("Singers added to table");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
My code is able to execute is able to run fine and does what it needs to, but I want to understand why and where I am not closing connections, and to understand how I can resolve this. Or at least understand if I am approaching this wrong.
In your case, you need to instantiate DBSinger class in try-with-resources statement to close the underlying connection.
Instead of doing:
DbSinger dbSinger = new DbSinger();
You need to do:
try (DbSinger dbSinger = new DbSinger()) {
// Your other code
}
This way the close() method you are overriding in your Db class will be called automatically.
Also, close the preparedStatement you created in your checkIfPopulated method by:
try (PreparedStatement ps = getConnection().prepareStatement(query)) {
// Other codes
}
Your code is old way. And you do need close manually. However, with Java 8, you can use try with resource like below,
try (Connection conn = ds.getConnection();
Statement stmt = conn.createStatement()) {
try {
stmt.execute(dropsql);
} catch (Exception ignore) {} // ignore if table not dropped
stmt.execute(createsql);
stmt.execute(insertsql);
try (ResultSet rs = stmt.executeQuery(selectsql)) {
rs.next();
} catch (Exception e2) {
e2.printStackTrace();
return("failed");
}
} catch(Exception e) {
e.printStackTrace();
return("failed");
}
I'm currently building a program with SQL in JavaFX.
I am calling DbConnect() in my main window and it all works fine.
The Issue I am having right now is to get this database connection in other classes (Another window/scene). Right now I am calling the DB connection function in all other scene's also which will cause issue's later on because it always opens a new database connection. So my question here is how can I share the database connection in all my classes.
Only want to open it once in main class and then inherit it to the other classes
Here is my Database connection function:
public Connection DbConnect() {
try {
con = DriverManager.getConnection(dbPath, user, pass);
statm = con.createStatement();
SetupSystem();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return con;
}
From the first window
// Initilizing all the database connections
public void DbConnect() {
con.DbConnect();
disconnectMenuItem.setVisible(true);
connectMenuItem.setVisible(false);
}
From the second Window (don't want to open new connection here) want to inherit connection from the first window
public void initialize(URL location, ResourceBundle resources) {
db.DbConnect();
}
thanks in advance,
You can use Singleton pattern that can share one instance through your classes:
public final class Singleton {
private static volatile Connection con = null;
private Singleton() {}
public static Singleton getInstance() {
if (con== null) {
try {
con = DriverManager.getConnection(dbPath, user, pass);
statm = con.createStatement();
SetupSystem();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return con;
}
}
Now,you can access to your database connection from the other classes like this :
public class ClassOne {
Singleton.getInstance();
}
public class ClassTwo {
Singleton.getInstance();
}
I have this class (my database connection factory):
#ApplicationScoped
public class ConnectionFactory {
private ComboPooledDataSource datasource;
private Long open = 0l;
private Long close = 0l;
#PostConstruct
public void init() throws PropertyVetoException, SQLException {
datasource = new ComboPooledDataSource();
datasource.setDriverClass("org.postgresql.Driver");
datasource.setJdbcUrl("jdbc:postgresql:dbcampanha");
datasource.setUser("postgres");
datasource.setPassword("admin");
datasource.setMinPoolSize(1);
datasource.setMaxPoolSize(5);
datasource.setCheckoutTimeout(30000);
datasource.setUnreturnedConnectionTimeout(30);
datasource.setMaxIdleTime(30);
datasource.setDebugUnreturnedConnectionStackTraces(true);
datasource.setAcquireIncrement(1);
}
#Produces
#RequestScoped
public Connection getConnection() throws ClassNotFoundException {
open++;
try {
Connection connection = datasource.getConnection();
connection.setAutoCommit(false);
return connection;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public void close(#Disposes Connection connection) {
close++;
try {
connection.commit();
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public Long getOpenedConnectionCounter() {
return open;
}
public Long getClosedConnectionCounter(){
return close;
}
public ComboPooledDataSource getDatasource(){
return datasource;
}
}
I use this class with an JAX-RS application. And for some tests using this route:
#RequestScoped
#Path("/test")
public class TesteService {
#Inject
private Connection connection;
#GET
#Produces(MyMediaType.JSON)
#Path("/yes")
public Response success() throws SQLException {
connection.getClientInfo("");
return Response.ok().build();
}
}
And this class for my Client:
public class TesteMain {
private static final String prefix = "http://localhost:8080/schoolwork/service/test/";
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10000; i++) {
Request request = new Request(prefix + "yes");
request.start();
if(i % 10 == 0)
Thread.sleep(1000l);
}
}
public static class Request extends Thread {
private String rota;
public Request(String rota){
this.rota = rota;
}
#Override
public void run() {
try {
HttpURLConnection url = (HttpURLConnection) (new URL(rota).openConnection());
url.connect();
System.out.println(url.getResponseCode() == 200 ? "SUCCESS" : "ERROR");
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
I receive this infos:
{
"opened-connection": 789,
"closed-connection": 867,
}
Yes, I have a number of closed database connection greater than opened. HOW? Any idea for this?
I use
Tomcat 7 + Java 7
P.S. I am sorry for my bad English :/
SOLVED
I change my counters for AtomicInteger objects, and works perfectly.
So, two quick comments:
Your counters are (boxed) longs read and updated willy-nilly by 1000 concurrent Threads. Their values will in general be unpredictable and nondeterministic. They certainly won't accurately count what you intend them to count. Consider using atomic operations on AtomicLongs instead.
Your commit() (or rollback()) should be attached to your database business logic, the part where you can tell a unit of work has either succeeded or failed. You shouldn't automatically commit on close.
I have created connection pool using ContextListener and host this project to tomcat.my config part as below.
in tomcat confg context.xml i have defined resource as below.
<Resource name="jdbc/TEST_DS"
auth="Container"
type="javax.sql.DataSource"
driverClassName="oracle.jdbc.driver.OracleDriver"
url="jdbc:oracle:thin:#server:1521/db"
username="uname"
password="pwd"
maxPoolSize="50"
removeAbandoned="true"
removeAbandonedTimeout="1000"
logAbandoned="true"
/>
now use this resource in ContextListener as below.
public class ConnectionListener implements ServletContextListener {
private DataSource dataSourceOracle = null;
private Connection connectionOracle = null;
private static final String ATTRIBUTE_NAME = "config";
public void contextDestroyed(ServletContextEvent sce) {
try {
if(connectionOracle!=null && !connectionOracle.isClosed() ){
this.connectionOracle.close();
this.connectionOracle = null;
}
ApplicationUtil.setServletContext(sce.getServletContext());
} catch (SQLException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
public void contextInitialized(ServletContextEvent event) {
ServletContext servletContext = event.getServletContext();
try {
String oracleDsName = servletContext.getInitParameter("oracle.ds.name");
Context ctx = new InitialContext();
Context envContext = (Context) ctx.lookup("java:/comp/env");
dataSourceOracle = (DataSource) envContext.lookup (oracleDsName);
connectionOracle = dataSourceOracle.getConnection();
System.out.println("testing Oracle connection >> "+connectionOracle);
ApplicationUtil.setServletContext(event.getServletContext());
} catch (SQLException e) {
e.printStackTrace();
}
catch (NamingException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
servletContext.setAttribute(ATTRIBUTE_NAME, this);
}
public Connection getOracleConnection() throws SQLException, ClassNotFoundException {
return this.connectionOracle;
}
public static ConnectionListener getInstance(ServletContext servletContext) {
return (ConnectionListener) servletContext.getAttribute(ATTRIBUTE_NAME);
}
}
now call this connection using method:
public class ApplicationUtil {
private static ServletContext context;
/* Called by Listener */
public static void setServletContext(ServletContext context){
ApplicationUtil.context = context;
}
/* Use this method to access context from any location */
public static ServletContext getServletContext(){
return ApplicationUtil.context;
}
}
public class DBAccess {
ServletContext context = null;
public DBAccess(ServletContext cnt) {
context = cnt;
}
public Connection getOracleConnection() throws SQLException, ClassNotFoundException {
return ConnectionListener.getInstance(context).getOracleConnection();
}
public List getLanguageList() {
Connection cn = getOracleConnection();
...
}
}
these all are what i have created to make connection pool. now the issue is when the server is down connection will be closed . and i need to restart tomcat every time to make connection pool again.
Is there any permanent solution to resolve this ??
Any suggestions will be appreciate.
Thanks in adv.
A db connection cannot survive the db. When the DB shuts down, the connection is lost.
Don't get and store the connection object in the context initializing.
Ask the pool for a connection every time you need it and after working on it, release it.
If sometimes the db is not startet, you get errors, that you must handle, but if the db restarts, you are able to get connections without restarting your tomat.