/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jdbi.v3.sqlobject;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import org.h2.jdbcx.JdbcDataSource;
import org.jdbi.v3.core.Handle;
import org.jdbi.v3.core.Jdbi;
import org.jdbi.v3.core.Something;
import org.jdbi.v3.core.transaction.TransactionException;
import org.jdbi.v3.core.mapper.SomethingMapper;
import org.jdbi.v3.core.transaction.TransactionIsolationLevel;
import org.jdbi.v3.sqlobject.customizer.BindBean;
import org.jdbi.v3.sqlobject.transaction.TransactionIsolation;
import org.jdbi.v3.sqlobject.transaction.Transactional;
import org.jdbi.v3.sqlobject.statement.SqlUpdate;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.google.common.collect.ImmutableSet;
public class TestTransactional
{
private Jdbi db;
private Handle handle;
private final AtomicBoolean inTransaction = new AtomicBoolean();
public interface TheBasics extends Transactional<TheBasics>
{
@SqlUpdate("insert into something (id, name) values (:id, :name)")
@TransactionIsolation(TransactionIsolationLevel.SERIALIZABLE)
int insert(@BindBean Something something);
}
@Test
public void testDoublyTransactional() throws Exception
{
final TheBasics dao = db.onDemand(TheBasics.class);
dao.inTransaction(TransactionIsolationLevel.SERIALIZABLE, transactional -> {
transactional.insert(new Something(1, "2"));
inTransaction.set(true);
transactional.insert(new Something(2, "3"));
inTransaction.set(false);
return null;
});
}
@Test(expected = TransactionException.class)
public void testOnDemandBeginTransaction() throws Exception {
// Calling methods like begin() on an on-demand Transactional SQL object makes no sense--the transaction would
// begin and the connection would just close.
// Jdbi should identify this scenario and throw an exception informing the user that they're not managing their
// transactions correctly.
db.onDemand(Transactional.class).begin();
}
@Before
public void setUp() throws Exception
{
final JdbcDataSource ds = new JdbcDataSource() {
private static final long serialVersionUID = 1L;
@Override
public Connection getConnection() throws SQLException
{
final Connection real = super.getConnection();
return (Connection) Proxy.newProxyInstance(real.getClass().getClassLoader(), new Class<?>[] {Connection.class}, new TxnIsolationCheckingInvocationHandler(real));
}
};
// in MVCC mode h2 doesn't shut down immediately on all connections closed, so need random db name
ds.setURL(String.format("jdbc:h2:mem:%s;MVCC=TRUE", UUID.randomUUID()));
db = Jdbi.create(ds);
db.installPlugin(new SqlObjectPlugin());
db.registerRowMapper(new SomethingMapper());
handle = db.open();
handle.execute("create table something (id int primary key, name varchar(100))");
}
@After
public void tearDown() throws Exception
{
handle.execute("drop table something");
handle.close();
}
private static final Set<Method> CHECKED_METHODS;
static {
try {
CHECKED_METHODS = ImmutableSet.of(Connection.class.getMethod("setTransactionIsolation", int.class));
} catch (final Exception e) {
throw new ExceptionInInitializerError(e);
}
}
private class TxnIsolationCheckingInvocationHandler implements InvocationHandler
{
private final Connection real;
public TxnIsolationCheckingInvocationHandler(Connection real)
{
this.real = real;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
if (CHECKED_METHODS.contains(method) && inTransaction.get()) {
throw new SQLException("PostgreSQL would not let you set the transaction isolation here");
}
return method.invoke(real, args);
}
}
}