This isn't about how to handle exceptions in Spring MVC or anything. I specifically need to handle an exception that can happen while spring is starting, i.e. before the whole application context is even initialised.
For a bit of background, the application in question is an IoT node that allows remote access to electronic equipment. It has a little h2 database built in to persist some data. That data is nice to have at some moments, but not really essential for the application to work.
It so happens that the device the application is running on can get its power cut every once in a while, and if that happens while there was a write operation to the database going on, the file is corrupt and a JdbcSQLException will be thrown when the application tries to boot again.
Since the data is not really essential, the easiest way to make the application work again is to just delete the database and let h2 recreate it. But in order to do that, I have to catch the exception so I can react to it. The application does not have to continue starting, it will be booted up again by systemd. I really just need to identify the exception and delete the file, that's it.
There is one obvious way to do it, which is to put SpringApplication.run in a try-catch block. But it's also really ugly, because I get the exception I'm looking for nested inside a gazillion spring exceptions that were caused by h2 failing to start.
It was also suggested that I catch the exception in the bean that instantiates the database, but unfortunately there is no bean instantiating it. The DB serves as a Quartz job-store and as such is fully managed by spring. Its entire presence in the code are the following entries in the properties file:
spring.quartz.job-store-type=jdbc
spring.quartz.properties.org.quartz.jobStore.misfireThreshold=900000
spring.datasource.name=h2
spring.datasource.url=jdbc:h2:file:${config.folder}controller
spring.datasource.driverClassName=org.h2.Driver
My question is, is there a way to register some kind of exception handler, or other means, to handle the exception directly when it happens, when I can identify it much more easily?
Depends how you've declared the bean. What's wrong with simply wrapping the bean like this?
#Configuration
class Conf {
#Bean
public DB foo() throws JdbcSQLException
{
try
{
return new DB();
}
catch(JdbcSQLException e)
{
deleteDatabase();
throw JdbcSQLException;
}
}
public static void deleteDatabase()
{
//...
}
}
Related
I have a bunch of Processors (basic data saving objects) that are currently in one big transaction. If one of them fails (duplicate data or whatever) I loose all the data.
I want to put the individual Processors into their own transactions so won't loose data. I can do that with annotations and all is fine. However, I have to catch the exceptions and only throw certain ones (like database down or some such). I want to eat the other errors (duplicates and bad messages and values and such) and just log them.
I found the ErrorHandler interface in Spring and the this thing: #ExceptionHandler(NullPointerException.class)
but I want something that is not tied to MVC. So I want something like this:
try {
<!--wrapped code (processor) -->
} catch (exception) {
<!--Exception handler code. -->
}
#ExceptionHandler(myexceptionHandler)
Also, I do understand that I can do this with AOP, but I would rather have a Spring stock class/annotation (I want someone else to do the work...)
It seems that Spring would have something like this built in, but I haven't found it. Any ideas?
I'm currently working on a java application. It's a standalone client with Spring and Hibernate. Also C3P0.
In the previous version we used a standard user(hardcoded in the configuration file) for the database connection but now we changed it so that every user has to provide his own credentials.
The beans with the code for the database are basically created on-demand.
I changed the XML-files and added a postprocessor which sets the credentials as well as some connection settings. It looks similar to this now:
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
configurer = new PropertyPlaceholderConfigurer();
// properties are retrieved from a private method
configurer.setProperties(getProperties());
context.addBeanFactoryPostProcessor(configurer);
context.setConfigLocations(new String[] { "first.xml","second.xml" });
context.refresh();
return context.getBean("myClass", MyClass.class);
This all works as expected but now we reach the part where I'm currently stuck.
I want to provide a test functionality for the connection, so that the user can click a button and then is told if his credentials were accepted or not.
My first idea was to do a simple select on the database. Sifting through the logs however, I noticed that Spring tries to connect to the database during the refresh() (or rather the instantiation of the bean) anyway. I can see exceptions in the logs, for example: java.sql.SQLException: Connections could not be acquired from the underlying database!
Unfortunately, Spring doesn't seem to actually care. The exceptions are logged away but refresh() finishes and is not throwing any exceptions because of this. I had hoped that I could simply catch the exception and then I would know that the connection failed.
I could just do the select as planned, but I want to limit the connection attempts as much as possible, because the database server will block the user after several attempts. Even permanently if there are to many attempts(already had some fun with that, before I changed the settings for the connection pool).
My searches regarding this problem came up with practically nothing. Is there a way to get the exception somehow? Or does Spring provide an API of sorts that would tell me about the connection error during the instantiation/refresh?
Failing that, any ideas for an alternative approach? Preferably one that needs only a single attempt to determine if a connection is possible.
Edit: For anyone interested: I went with the suggestion from Santosh and implemented a connection test in JDBC.
Unfortunately there seems to be no easy way to make use of the database errors/exceptions encountered during the bean instantiation.
The kind of functionality you are looking for would be very tricky to accomplish using spring+hibernate.
The connection properties are set at the session-factory level and if credentials are incorrect, the session-factory is not instantiated.
Quoting #Bozo from his answer here.
What you can do is extend LocalSessionFactoryBean and override the
getObject() method, and make it return a proxy (via
java.lang.reflect.Proxy or CGLIB / javassist), in case the
sessionFactory is null. That way a SessionFactory will be injected.
The proxy should hold a reference to a bare SessionFactory, which
would initially be null. Whenever the proxy is asked to connect, if
the sessionFacotry is still null, you call the buildSessionFactory()
(of the LocalSessionFactoryBean) and delegate to it. Otherwise throw
an exception. (Then of course map your new factory bean instead of the
current)
There is also a simple and rudimentary approach wherein before creating ClassPathXmlApplicationContext, simply try to obtain a connection using raw JDBC calls. If that succeed then proceed or else give use appropriate message.
You can limit the connection attempts here as you are in full control.
I'm currently working with MyBatis-Spring integration framework and that's what I read from docs:
Rather than code data access objects (DAOs) manually using SqlSessionDaoSupport or SqlSessionTemplate,
Mybatis-Spring provides a proxy factory: MapperFactoryBean. This class lets you inject data mapper interfaces
directly into your service beans. When using mappers you simply call them as you have always called your
DAOs, but you won't need to code any DAO implementation because MyBatis-Spring will create a proxy for
you.
That's a very nice feature... but what about exception handling? Where should I translate SQL errors? In my service layer? But wouldn't it violate service-DAO patterns?
Example:
public final class AccountServiceImpl implements AccountService {
(...)
private AccountMapper accountMapper;
(...)
#Override
public void addAccount(Account account) throws AccountServiceException {
//Validating, processing, setting timestamps etc.
(...)
//Persistence:
int rowsAffected;
try {
rowsAffected = accountMapper.insertAccount(account);
} catch (Exception e) {
String msg = e.getMessage();
if (msg.contains("accounts_pkey"))
throw new AccountServiceException("Username already exists!");
if (msg.contains("accounts_email_key"))
throw new AccountServiceException("E-mail already exists!");
throw new AccountServiceException(APP_ERROR);
}
LOG.debug("Rows affected: '{}'", rowsAffected);
if (rowsAffected != 1)
throw new AccountServiceException(APP_ERROR);
}
Is it OK to translate exceptions in service layer?
How should it be done?
Thanks in advance for you advice.
Having recently used mybatis-spring for a project I came across the same stumbling block. I also didn't want to litter my service class with DAO exception handling, particularly since some methods in my service layer required read-only access to a lot of different tables.
The solution I arrived at was to catch the exceptions in the service layer but create your own exception type that takes the caught exception as a parameter. This can then filter out what kind of error message should be contained when the exception is actually constructed and remove the need for string matching (in the service layer at least).
You are close to that there, except the AccountServiceException would have a constructor that took the Exception e as a parameter. I also chose to try and do all my data access as early as possible and wrap it all in a single try/catch. Since the MapperFactoryBean always translates thrown exceptions in to Spring DataAccessExceptions you don't have to worry about catching other kinds of exceptions when doing data access.
I hesitate to consider this an answer as such - more of a sharing of experience given I came across that and hesitated as well.
Translating low level DataAccessExceptions thrown by MyBatis to application-defined ones in service layer is a standard practice.
It's usually connected to transaction handling as you can't handle the transaction spanning multiple DAOs in DA layer.
So yes it's OK and even recommended.
Normally I log the exceptions thrown by DAO in error log and rethrow something defined by application.
I have some EJBs that use Hibernate to persist data to the database. I have a thick Swing client that talks to these EJBs. The client knows nothing about the database (no driver jar).
During one transaction a Hibernate ConstraintViolationException can be thrown. I catch all exceptions and wrap them in an EJBException like so:
catch(HibernateException e) {
e.printStackTrace();
throw new EJBException(e);
}
The problem I am getting is that when the exception is unmarshalled by the JBoss Invoker on the client side, a ClassNotFoundException is thrown (for PSQLException) since the client has no sql driver jar in the classpath.
I changed this application to always pass the caught exception to the ejbexception constructor like this so we could have a stack trace history. Now I am finding why the original developers didn't do this.
At this point I see two options - either include the postgres driver jar with the client, or remove passing the caught exception to the EJBException constructor. I am curious if anyone has any other suggestions and also how others handle exceptions in their EJBs?
My take is that the client, end user, doesn't need to know the technical details of the problem. Hence at various layer boundaries it's quite reasonble to convert a technical exception to a general "An error of nature XYZ ocurred".
A scheme I've seen used is for the server to allocate a unique error number at the point the exception is detected. It then writes diagnistics to its logs including that number. Messages reported to the client simply include the number. A support desk can then correlate the user's report of the issue via that specific error number.
I have an EJB3 application which consists of some EJB's for accessing a DB, and exposed via a Session Bean as a web service.
Now there are two things I need to find out:
1) Is there any way I can stop SQL exceptions from causing the web service from throwing a SOAP Fault? The transactions are handled by the container, and currently sql exceptions cause a RollBackException to be thrown, and consequently the transaction to be rolled back (desired behaviour) and the web service to throw a fault (not desired).
2) I wish to extend the webservice to be able to take in a list of entities, and the session bean to persist each. However, I want each entity to be executed in its own transaction, so that if one fails the others are not affected (and again the web service should not fault).
For (1) I have tried to catch the RollBackException, but I assume this is thrown somewhere on another thread, as the catch block is never reached. I assume for (2) I will need to look into User Transactions, but firstly would prefer the container to manage this, and secondly do not know how to force the use of user transactions.
Thanks.
no, you can do all this with container managed transactions (and this is definitely preferable, as managing transactions is a pain).
the gist of the solution is to create a second EJB with a local interface only and the transaction semantics you desire. then your "public" ejb, which the web-service is calling directly, calls into this second ejb via its local interface to do the actual work.
something along the lines of:
public class MyPublicEjb {
#EJB
private MyPrivateImpl impl;
public void doSomething() {
try {
impl.doSomething();
} catch(TXRolledBack) {
// handle rollback ...
}
}
}
I know this looks sort of ugly, but trust me, this is far preferable to directly manipulating transactions.
For (1): Debug your code to find out where the exception is being thrown and what is causing it. Then handle the exception there.
For (2): Wrap each instance with beginTransaction() and commit().
for(each Entity){
try{
//begin transaction
//save entity
//commit
} catch(Exception e) {
//handle Exception, but continue on
}
}