/* * 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 static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.jdbi.v3.core.transaction.TransactionIsolationLevel.READ_COMMITTED; import static org.jdbi.v3.core.transaction.TransactionIsolationLevel.READ_UNCOMMITTED; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import java.util.List; import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.Something; import org.jdbi.v3.core.mapper.SomethingMapper; import org.jdbi.v3.core.rule.H2DatabaseRule; import org.jdbi.v3.core.transaction.TransactionException; import org.jdbi.v3.sqlobject.config.RegisterBeanMapper; import org.jdbi.v3.sqlobject.config.RegisterRowMapper; import org.jdbi.v3.sqlobject.customizer.Bind; import org.jdbi.v3.sqlobject.customizer.BindBean; import org.jdbi.v3.sqlobject.customizer.Define; import org.jdbi.v3.sqlobject.customizer.MaxRows; import org.jdbi.v3.sqlobject.statement.SqlQuery; import org.jdbi.v3.sqlobject.statement.SqlUpdate; import org.jdbi.v3.sqlobject.subpackage.BrokenDao; import org.jdbi.v3.sqlobject.subpackage.SomethingDao; import org.jdbi.v3.sqlobject.transaction.Transaction; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.Mockito; public class TestSqlObject { @Rule public H2DatabaseRule dbRule = new H2DatabaseRule().withPlugin(new SqlObjectPlugin()); @Rule public ExpectedException exception = ExpectedException.none(); private Handle handle; @Before public void setUp() throws Exception { handle = dbRule.getSharedHandle(); } @Test public void testPassThroughMethod() throws Exception { Dao dao = handle.attach(Dao.class); dao.insert(3, "Cora"); Something c = dao.findByIdHeeHee(3); assertThat(c).isEqualTo(new Something(3, "Cora")); } @Test public void testUnimplementedMethod() throws Exception { exception.expect(IllegalStateException.class); exception.expectMessage("Method UnimplementedDao.totallyBroken must be default " + "or be annotated with a SQL method annotation."); handle.attach(UnimplementedDao.class); } @Test public void testRedundantMethodHasDefaultImplementAndAlsoSqlMethodAnnotation() throws Exception { exception.expect(IllegalStateException.class); exception.expectMessage("Default method RedundantDao.list has @SqlQuery annotation. " + "SQL object methods may be default, or have a SQL method annotation, but not both."); handle.attach(RedundantDao.class); } @Test public void testPassThroughMethodWithDaoInAnotherPackage() throws Exception { SomethingDao dao = handle.attach(SomethingDao.class); dao.insert(3, "Cora"); Something c = dao.findByIdHeeHee(3); assertThat(c).isEqualTo(new Something(3, "Cora")); } @Test(expected = IllegalStateException.class) public void testUnimplementedMethodWithDaoInAnotherPackage() throws Exception { BrokenDao dao = handle.attach(BrokenDao.class); } @Test public void testSimpleTransactionsSucceed() throws Exception { SomethingDao dao = dbRule.getJdbi().onDemand(SomethingDao.class); dao.insertInSingleTransaction(10, "Linda"); } @Test public void testTransactionAnnotationWorksOnInterfaceDefaultMethod() throws Exception { Dao dao = dbRule.getSharedHandle().attach(Dao.class); assertThat(dao.doesTransactionAnnotationWork()).isTrue(); } @Test public void testNestedTransactionsCollapseIntoSingleTransaction() { Handle handle = Mockito.spy(dbRule.getSharedHandle()); Dao dao = handle.attach(Dao.class); dao.threeNestedTransactions(); verify(handle, times(1)).begin(); verify(handle, times(1)).commit(); dao.twoNestedTransactions(); verify(handle, times(2)).begin(); verify(handle, times(2)).commit(); } @Test public void testNestedTransactionWithSameIsolation() { Handle handle = Mockito.spy(dbRule.getSharedHandle()); Dao dao = handle.attach(Dao.class); dao.nestedTransactionWithSameIsolation(); verify(handle, times(1)).begin(); verify(handle, times(1)).commit(); } @Test(expected = TransactionException.class) public void testNestedTransactionWithDifferentIsoltion() { Handle handle = Mockito.spy(dbRule.getSharedHandle()); Dao dao = handle.attach(Dao.class); dao.nestedTransactionWithDifferentIsolation(); } @Test public void testSqlUpdateWithTransaction() { Handle handle = Mockito.spy(dbRule.getSharedHandle()); Dao dao = handle.attach(Dao.class); dao.insert(1, "foo"); verify(handle, never()).begin(); assertThat(dao.findById(1)).isEqualTo(new Something(1, "foo")); assertThat(dao.insertTransactional(2, "bar")).isEqualTo(1); verify(handle, times(1)).begin(); assertThat(dao.findById(2)).isEqualTo(new Something(2, "bar")); } @Test public void testRedundantMethodCustomizingAnnotation() { exception.expect(IllegalStateException.class); exception.expectMessage("Statement customizing annotations don't work on default methods."); handle.attach(RedundantMethodStatementCustomizingAnnotation.class); } @Test public void testRedundantParameterCustomizingAnnotation() { exception.expect(IllegalStateException.class); exception.expectMessage("Statement customizing annotations don't work on default methods."); handle.attach(RedundantParameterStatementCustomizingAnnotation.class); } @Test public void testRedundantParameterBindingAnnotation() { exception.expect(IllegalStateException.class); exception.expectMessage("Statement customizing annotations don't work on default methods."); handle.attach(RedundantParameterBindingAnnotation.class); } @Test public void testBooleanReturn() { Dao dao = handle.attach(Dao.class); assertThat(dao.insert(1, "a")).isTrue(); assertThat(dao.update(2, "b")).isFalse(); } @Test public void testSubInterfaceOverridesSuperMethods() { SubclassDao dao = handle.attach(SubclassDao.class); dao.insert(new Something(1, "foo")); assertThat(dao.get(1)).isEqualTo(new Something(1, "foo")); } @RegisterRowMapper(SomethingMapper.class) public interface Dao extends SqlObject { @SqlUpdate("insert into something (id, name) values (:id, :name)") boolean insert(@Bind("id") int id, @Bind("name") String name); @SqlUpdate("update something set name=:name where id=:id") boolean update(int id, String name); @SqlQuery("select id, name from something where id = :id") Something findById(@Bind("id") int id); @Transaction @SqlUpdate("insert into something (id, name) values (:id, :name)") Integer insertTransactional(@Bind("id") int id, @Bind("name") String name); default Something findByIdHeeHee(int id) { return findById(id); } @Transaction default void threeNestedTransactions() { twoNestedTransactions(); } @Transaction default void twoNestedTransactions() { assertThat(doesTransactionAnnotationWork()).isTrue(); } @Transaction default boolean doesTransactionAnnotationWork() { return getHandle().isInTransaction(); } @Transaction(READ_UNCOMMITTED) default boolean transactionWithIsolation() { return getHandle().isInTransaction(); } @Transaction(READ_UNCOMMITTED) default void nestedTransactionWithSameIsolation() { assertThat(transactionWithIsolation()).isTrue(); } @Transaction(READ_COMMITTED) default void nestedTransactionWithDifferentIsolation() { transactionWithIsolation(); } } public interface UnimplementedDao extends SqlObject { void totallyBroken(); } public interface RedundantDao extends SqlObject { @SqlQuery("select * from something") @RegisterRowMapper(SomethingMapper.class) default List<Something> list() { return getHandle().createQuery("select * from something") .map(new SomethingMapper()) .list(); } } public interface RedundantMethodStatementCustomizingAnnotation extends SqlObject { @MaxRows(10) default List<String> broken() { return emptyList(); } } public interface RedundantParameterStatementCustomizingAnnotation extends SqlObject { default List<String> broken(@Define int wut) { return emptyList(); } } public interface RedundantParameterBindingAnnotation extends SqlObject { default String broken(@Bind int wat) { return "foo"; } } public interface BaseDao<T> { void insert(T obj); T get(long id); } public interface SubclassDao extends BaseDao<Something> { @Override @SqlUpdate("insert into something (id, name) values (:id, :name)") void insert(@BindBean Something something); @Override @SqlQuery("select * from something where id = :id") @RegisterBeanMapper(Something.class) Something get(long id); } }