Good morning,
yesterday I used for the first time MyBatis. As a starting point I used the example from Loiane Groner. And I tried to replace the mysql db with an internal hsqldb (v1.8). I changed everything but I never got the insert unit test to work as expected. See below, first all necessary parts.
<insert id="insert" parameterType="Contact">
INSERT INTO CONTACT ( CONTACT_EMAIL, CONTACT_NAME, CONTACT_PHONE )
VALUES ( #{email}, #{name}, #{phone} );
</insert>
public void insert(Contact contact){
SqlSession session = sqlSessionFactory.openSession();
try {
session.insert("Contact.insert", contact);
session.commit();
} finally {
session.close();
}
}
#Test
public void testInsert() {
Contact actual = new Contact();
actual.setName("Adam");
actual.setPhone("+001 811 23456");
actual.setEmail("anonym#gmail.com");
contactDAO.insert( actual );
assertEquals(1, contactDAO.selectAll().size() );
}
This test will pass, because with the select method I'll retrieve the contact I inserted before. But if I open the hsqldb there is no contact ( entry ) in.
I acutally would expect, that this test would only pass once. And if I call it a second time there should be a more than one entry. But this dosn't happen. Why, doesn't stay the contact permant? (There is no cleanup method)
This is because your settings for HSQLDB are the default settings.
With default settings, the database has a WRITE DELAY. This is normally fine for application embedded databases, but you need to turn off WRITE DELAY for testing if you expect the changes to be persisted immediately. Use hsqldb.write_delay=false as a connection property.
An alternative is to SHUTDOWN the database at the end of the test. You can add the connection property shutdown=true and explicitly close all your database connections at the end of the test.
These properties are the same in HSQLDB 1.8 and 2.x and documented here:
http://hsqldb.org/doc/2.0/guide/dbproperties-chapt.html
I'm guessing that the issue has to do with the try...finally block in the insert method. Personally, I think that leaving out even a catch(Exception e){log.error(e)} is bad policy and a disservice.
I don't know about hsqldb specifically, but, I have seen, in certain DB's, if an error happens during the call to "commit", it will continue to show rows which shouldn't exist. I'll bet that's what's happening here.
Try adding catch(Exception e){log.error(e)} before the finally in the insert method.
Related
I've been struggling for few hours with this one and could do with some help.
A client sends an object that contains a list;
One of the objects in the list has been modified on the client;
In some cases I don't want that modified entity to be persisted to the database, I want to keep the original database values.
I have tried the following and various attempts to clear(), refresh() and flush() the session:
List<Integer> notToModifyIds = dao.getDoNotModifyIds(parentEntity.getId());
MyEntityFromList entityFromClient, entityFromDb;
for(Integer notToModifyId : notToModifyIds){
ListIterator iterator = parentEntity.getEntities().listIterator();
while(iterator.hasNext()){
entityFromClient = (MyEntity) iterator.next();
if(Objects.equals(entityFromClient.getId(), notToModifyId)){
dao.evict(entityFromClient);
entityFromDb = (MyEntity) dao.get(MyEntity.class, notToModifyId);
iterator.remove(entityFromClient);
iterator.add(entityFromDb);
}
}
}
However, no matter what I try I always get the values from the client persisted to the database. When I add a breakpoint after iterator.add() I can check that the database value has not been updated at that point, hence I know that if I could load the entity from the DB then I would have the value I want.
I'm feeling a little suppid!
I don't know if I got the whole scenario here. Are those modified "entitiesFromClient" attached to the Hibernate session? If they are, the changes were probably automatically flushed to the database before you "evicted" them.
Setting a MANUAL flush mode would help you avoid the automatic behaviour.
First of all, I would enable the Hibernate SQL logging to see more precisely what is happening. See Enable Hibernate logging.
Checking the database in another session (while stopped in the breakpoint) will not help if this code is running within a transaction. Even if the change was already flushed in the database you wouldn't see it until the transaction is commited.
I am using spring, hibernate and postgreSQL.
Let's say I have a table looking like this:
CREATE TABLE test
(
id integer NOT NULL
name character(10)
CONSTRAINT test_unique UNIQUE (id)
)
So always when I am inserting record the attribute id should be unique
I would like to know what is better way to insert new record (in my spring java app):
1) Check if record with given id exists and if it doesn't insert record, something like this:
if(testDao.find(id) == null) {
Test test = new Test(Integer id, String name);
testeDao.create(test);
}
2) Call straight create method and wait if it will throw DataAccessException...
Test test = new Test(Integer id, String name);
try{
testeDao.create(test);
}
catch(DataAccessException e){
System.out.println("Error inserting record");
}
I consider the 1st way appropriate but it means more processing for DB. What is your opinion?
Thank you in advance for any advice.
Option (2) is subject to a race condition, where a concurrent session could create the record between checking for it and inserting it. This window is longer than you might expect because the record might be already inserted by another transaction, but not yet committed.
Option (1) is better, but will result in a lot of noise in the PostgreSQL error logs.
The best way is to use PostgreSQL 9.5's INSERT ... ON CONFLICT ... support to do a reliable, race-condition-free insert-if-not-exists operation.
On older versions you can use a loop in plpgsql.
Both those options require use of native queries, of course.
Depends on the source of your ID. If you generate it yourself you can assert uniqueness and rely on catching an exception, e.g. http://docs.oracle.com/javase/1.5.0/docs/api/java/util/UUID.html
Another way would be to let Postgres generate the ID using the SERIAL data type
http://www.postgresql.org/docs/8.1/interactive/datatype.html#DATATYPE-SERIAL
If you have to take over from an untrusted source, do the prior check.
I use MyBatis 3.1.
I have two use cases when I need to bypass MyBatis local cache and directly hit the DB.
Since MyBatis configuration file only have global settings, it is not applicable to my case, because I need it as an exception, not as a default. Attributes of MyBatis <select> XML statement do not seem to include this option.
Use case 1: 'select sysdate from dual'.
MyBatis caching causes this one to always return the same value within a MyBatis session. This causes an issue in my integration test, when I try to replicate a situation of an outdated entry.
My workaround was just to use a plain JDBC call.
Use case 2: 'select' from one thread does not always see the value written by another thread.
Thread 1:
SomeObject stored = dao.insertSomeObject(obj);
runInAnotherThread(stored.getId());
//complete and commit
Thread 2:
//'id' received as an argument provided to 'runInAnotherThread(...)'
SomeObject stored = dao.findById(id);
int count = 0;
while(stored == null && count < 300) {
++count;
Thread.sleep(1000);
stored = dao.findById(id);
}
if (stored == null) {
throw new MyException("There is no SomeObject with id="+id);
}
I occasionally receive MyException errors on a server, but can't reproduce on my local machine. In all cases the object is always in the DB. So I guess the error depends on whether the stored object was in MyBatis local cache at the first time, and waiting for 5 minutes does not help, since it never checks the actual DB.
So my question is how to solve the above use cases within MyBatis without falling back to the plain JDBC?
Being able just to somehow signal MyBatis not to use a cached value in a specific call (the best) or in all calls to a specific query would be the preferred option, but I will consider any workaround as well.
I don't know a way to bypass local cache but there are two options how to achieve what you need.
The first option is to set flushCache="true" on select. This will clear the cache after statement execution so next query will hit database.
<select id="getCurrentDate" resultType="date" flushCache="true">
SELECT SYSDATE FROM DUAL
</select>
Another option is to use STATEMENT level local cache. By default local cache is used during SESSION (which is typically translates to transaction). This is specified by localCacheScope option and is set per session factory. So this will affect all queries using this mybatis session factory.
Let me summarize.
The solution from the previous answer, 'flushCache="true"' option on the query, works and solves both use cases. It will flush cache after every such 'select', so the next 'select' statement will hit the DB. Although it works after the 'select' statement is executed, it's OK since the cache is empty anyway before the first 'select'.
Another solution is to start a new session. I use Spring, so it's enough to mark a method with #Transactional(propagation = Propagation.REQUIRES_NEW). Since MyBatis session is tied to Spring transaction, this will cause to create another MyBatis session with fresh cache every time the method is called.
By some reason, the MyBatis option 'useCache="false"' in the query does not work.
The following Options annotation can be used:
#Options(useCache=false, flushCache=FlushCachePolicy.TRUE)
Apart from answers by Roman and Alexander there is one more solution for this:
Configuration configuration = MyBatisUtil.getSqlSessionFactory().getConfiguration();
Collection<Cache> caches = configuration.getCaches();
//If you have multiple caches and want a particular to get deleted.
// Cache cache = configuration.getCache("PPL"); // namespace of particular XML
for (Cache cache : caches) {
Lock w = cache.getReadWriteLock().writeLock();
w.lock();
try {
cache.clear();
} finally {
w.unlock();
}
}
I'm writing a project for college and I've encountered some strange phenomena.
The program supposed to serve a restaurant so it has a server side that manages all the needs of the different front ends. the different front ends are "dinner terminal", "kitchen terminal", "waiter terminal" and an "admin terminal".
When I add an object to the DB I see it in the DB and the kitchen terminal receives it and I see that the object it gets is the right one.
public void addSessionOrder(String id, SessionOrder sessionOrder)
{
Session context = clientSessions.get(id);
context.beginTransaction();
context.save(sessionOrder);
context.getTransaction()
.commit();
}
notice that each terminal (connection) has it's own session in hibernate.
however, once I try to update the status of a sessionorder i get this exception
java.lang.NullPointerException
at database.DatabaseContext.updateSessionOrderStatus(DatabaseContext.java:170)
at protocol.handlers.UpdateSessionOrderStatusHandler.handle(UpdateSessionOrderStatusHandler.java:35)
at server.ResturantServer.handleClientMessage(ResturantServer.java:126)
at server.ConnectionManager.handleClientMessage(ConnectionManager.java:86)
at server.SockJSSocketHandler$3.handle(SockJSSocketHandler.java:55)
this is the method:
public void updateSessionOrderStatus(String id, SessionOrder order, OrderStatus newStatus)
{
Session context = clientSessions.get(id);
context.beginTransaction();
SessionOrder ord = (SessionOrder)context.get(SessionOrder.class, order.getOrderId());
ord.setStatus(newStatus);
context.update(ord);
context.getTransaction()
.commit();
}
The line that throws the exception is "ord.setStatus(newStatus);"
after debugging this is the info I have:
fields id and sessionOrder contain legit data and are initiated as needed.
sessionOrder.getOrderId() returns the needed ID for a corresponding object in the DB (it exists)
the query on the DB return null to ord.
Another thing I've noticed, if I turn the server off (kill hibernate) and restart everything, the whole things works fine. So I believe it has something to do with the fact that some session X inserted the object to the DB, and some other session Y tries to retrieve it afterwards.
the sessions are different and originate from different connections, and the specific order exists in the DB so I see no reason for it not to return the value.
I think it has something to do with caching of the model, however I'm quite a noob in hibernate so I can't pin point the problem.
sessionOrder.getOrderId() returns the needed ID for a corresponding object in the DB (it exists) the query on the DB return null to ord.
The ord object is null so it throws a NPE. I guess that hibernate cannot find the order with the given ID.
Add some logging here and you'll see what causes you trouble
I'm having some trouble with EclipseLink. My program has to interact with a database (representing a building). I've written a little input-testmode where I can manually insert stuff through the console.
My problem: a normal getByID-operation works just fine if I try to retrieve an entity I previously inserted through EclipseLink itself (by commit()), but throws a NoResultException when trying to select a row manually inserted via SQL-script (building -> lots of rooms -> script).
This (oversemplified) works fine:
main() {
MyRoom r = new MyRoom();
r.setID("floor1-roomnr4");
em.commit(r); //entity manager
DAO.getRoomByID("floor1-roomnr4"); // works
}
and the combination of generation-script + simply getRoomByID() throws an exception.
If I try it in SQL Developer I get the result I want for the exact select statement which just threw a NoResultException. I also only get this problem in the input-mode, otherwise selecting the generated rows works also fine.
Does EclipseLink have some cache-mechanism I'm unaware of which is causing some problem?
Are you sure EclipseLink and SQL Developer are connected to the same Database? Please verify the connection information for both. Is the generation-script committing the changes with the "commit" command?
If EclipseLink works similarly to Hibernate then yes there is a cache. The "first level cache" guaranties that you get the exact same instance within one transaction which makes sense. If you know EclipseLink/transactions then try to evict all loaded instances or commit the transaction and then try your DAO again. This would force EclipseLink to fetch the data from the database again
See Answer to similar question