Tags: java querydsl spring springboot
先週の前編からの続きです
SQLExpressionsクラスを使えば直感的に書けます。
@Test
public void subQuerySample() {
List<Author> list = sqlQueryFactory.select(qAuthor)
.from(qAuthor)
.where(SQLExpressions.select(qBook.count()).from(qBook).where(qBook.authorId.eq(qAuthor.id)).gt(0L))
.fetch();
log.info(list.toString());
}
$ mvn test -Dtest=ReadTest#subQuerySample
Running com.example.ReadTest
[INFO] jdbc.sqlonly main select AUTHOR.ID, AUTHOR.NAME from AUTHOR AUTHOR where (select count(BOOK.ISBN) from BOOK BOOK
where BOOK.AUTHOR_ID = AUTHOR.ID) > 0
[INFO] com.example.ReadTest main [id = 1, name = Arthur Conan Doyle, id = 2, name = Haruki Murakami, id = 3, name = 江戸川乱歩]
なんの変哲もない感じですが。
@Test
public void select_between() {
SQLQuery<Book> query = sqlQueryFactory.select(qBook);
query.from(qBook);
java.sql.Date from = Date.valueOf("1980-01-01");
java.sql.Date to = Date.valueOf("2000-01-01");
query.where(qBook.publishDate.between(from, to));
List books = query.fetch();
books.forEach(book -> log.info(book.toString()));
}
$ mvn test -Dtest=ReadTest#select_between
[INFO] jdbc.sqlonly main select BOOK.AUTHOR_ID, BOOK.ISBN, BOOK.PUBLISH_DATE, BOOK.TITLE from BOOK BOOK where BOOK.PUBLISH_DATE
between '01/01/1980 00:00:00.000' and '01/01/2000 00:00:00.000'
[INFO] com.example.ReadTest main authorId = 2, isbn = 001-9999990001, publishDate = 1987-01-01, title = Norwegian Wood
RDBMSの種類によって、SQLの書き方にはそれぞれ方言や独自の関数があります。ほとんどの場合はQueryDSLが提供するSQLTemplateが吸収してくれますが、それではカバーしきれない独特のSQLを書かねばならないことが、実際の開発現場でもよくあります。そんなときでもQueryDSLは柔軟に対応できます。下の例では、between句が特殊な関数だと仮定して、それを通常のプレースホルダつきの文字列としてQueryDSLでのSQL組み立てに加えています。
@Test
public void select_between2() {
SQLQuery<Book> query = sqlQueryFactory.select(qBook);
query.from(qBook);
// The case that you need special dialect.
// (supposing that "BETWEEN" is a dialect.)
String template = "publish_date between {0} and {1}";
query.where(Expressions.booleanTemplate(template, "1980-01-01", "2000-12-31"));
List books = query.fetch();
books.forEach(book -> log.info(book.toString()));
}
一つ前で紹介した通常のbetween句用のメソッドと違い、文字列そのままでSQLを組み立てるので、
between '1980-01-01' and '2000-12-31'
のように時刻情報は無い状態でのbetween句となります。
$ mvn test -Dtest=ReadTest#select_between2
[INFO] jdbc.sqlonly main select BOOK.AUTHOR_ID, BOOK.ISBN, BOOK.PUBLISH_DATE, BOOK.TITLE from BOOK BOOK where publish_date
between '1980-01-01' and '2000-12-31'
[INFO] com.example.ReadTest main authorId = 2, isbn = 001-9999990001, publishDate = 1987-01-01, title = Norwegian Wood
ここまですべてSELECTでした。INSERTはどうかというと、見ての通りです。INSERTしたい情報をそのテーブル用のBeanにつめこんで、populate()メソッドに渡してexecute()することでINSERT文を発行できます。
@Test
@Transactional()
public void insert1() {
Book book = new Book();
book.setIsbn("999-9999999999");
book.setAuthorId(4);
book.setTitle("進撃の巨人 (1)");
java.sql.Date publishDate = java.sql.Date.valueOf(LocalDate.parse("2010-03-17"));
book.setPublishDate(publishDate);
sqlQueryFactory.insert(qBook).populate(book).execute();
}
$ mvn test -Dtest=CreateTest#insert1
Running com.example.CreateTest
[INFO] jdbc.sqlonly main insert into BOOK (AUTHOR_ID, ISBN, PUBLISH_DATE, TITLE)
values (4, '999-9999999999', '03/17/2010 00:00:00.000', '進撃の巨人 (1)')
下のように書いても同じです。ただしsetメソッドを呼び出した順でカラムが指定されます。(あまり意味は無いですが)
public void insert2() {
java.sql.Date publishDate = java.sql.Date.valueOf(LocalDate.parse("2010-03-17"));
SQLInsertClause insert = sqlQueryFactory.insert(qBook)
.set(qBook.isbn, "999-9999999999")
.set(qBook.title, "Attack of Titan vol 1")
.set(qBook.authorId, 4)
.set(qBook.publishDate, publishDate);
insert.execute();
}
$ mvn test -Dtest=CreateTest#insert2
Running com.example.CreateTest
[INFO] jdbc.sqlonly main insert into BOOK (ISBN, TITLE, AUTHOR_ID, PUBLISH_DATE)
values ('999-9999999999', 'Attack of Titan vol 1', 4, '03/17/2010 00:00:00.000')
UPDATE, DELETEも似たような感じで書けてしまうのでここでは省略です。
すでに開発中/運用中のアプリケーションでは他のO/Rマッパーが使われていることでしょう。それがSpringフレームワークに沿っている場合なら、Springの使い方一つで簡単に併用できます。下の例では、
@Test
@Transactional
public void multiple_ORMapper() {
// insert by jdbcTemplate
String insertSql = "insert into book (isbn,title,author_id,publish_date) " +
"values ('001-0000000003', 'TThe Hound of the Baskervilles', 1, '1901-01-01')";
jdbcTemplate.execute(insertSql);
// update the above record by QueryDSL
sqlQueryFactory
.update(qBook)
.set(qBook.title, "The Hound of the Baskervilles")
.where(qBook.isbn.eq("001-0000000003"))
.execute();
// select by jdbcTemplate
String selectSql = "select isbn from book where isbn='001-0000000003'";
Map result = jdbcTemplate.queryForMap(selectSql);
log.info(result.toString());
}
$ mvn test -Dtest=CreateTest#multiple_ORMapper
[INFO] jdbc.sqlonly main insert into book (isbn,title,author_id,publish_date) values ('001-0000000003', 'TThe Hound
of the Baskervilles', 1, '1901-01-01')
[INFO] jdbc.sqlonly main update BOOK set TITLE = 'The Hound of the Baskervilles' where BOOK.ISBN = '001-0000000003'
[INFO] jdbc.sqlonly main select isbn from book where isbn='001-0000000003'
[INFO] com.example.CreateTest main {ISBN=001-0000000003}
想定通りに動いています。もちろん途中で例外が発生した場合はINSERTもUPDATEも、@Transactionalがついたこのメソッド内のすべてのSQLの発行がロールバックされます。どんなO/Rマッパーを使うのであれ、それに与えるDataSourceオブジェクトをTransactionAwareDataSourceProxyで包み込んでおくことでトランザクション管理をSpringの支配下に置いておくのがポイントです。
@EnableAutoConfiguration
@Configuration
public class Config {
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
builder.setType(EmbeddedDatabaseType.H2);
builder.addScript("classpath:sql/00_init.sql");
EmbeddedDatabase ds = builder.build();
net.sf.log4jdbc.sql.jdbcapi.DataSourceSpy proxyDs = new net.sf.log4jdbc.sql.jdbcapi.DataSourceSpy(ds);
return new TransactionAwareDataSourceProxy(proxyDs);
}
@Bean
public JdbcTemplate jdbcTempate() {
JdbcTemplate template = new JdbcTemplate();
template.setDataSource(dataSource());
return template;
}
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
@Bean
public com.querydsl.sql.Configuration querydslConfiguration() {
SQLTemplates templates = H2Templates.builder().build(); //change to your Templates
com.querydsl.sql.Configuration configuration = new com.querydsl.sql.Configuration(templates);
configuration.setExceptionTranslator(new SpringExceptionTranslator());
return configuration;
}
@Bean
public SQLQueryFactory sqlQueryFactory() {
Provider provider = new SpringConnectionProvider(dataSource());
return new SQLQueryFactory(querydslConfiguration(), provider);
}
}
上のSpringのJavaConfigをよく見ると、QueryDSL用のSQLQueryFactoryもJdbcTemplateも、与えられるdataSource()は一つのメソッドから発生するものに集約されていて、それはTransactionAwareDataSourceProxyに包まれています。
今回紹介したのはquerydsl-sqlですが、querydsl-jpa, querydsl-lucene, querydsl-mongodbなどいろいろあるようです。リファレンスが充実しているので詳しくはそちらで。
念のためタグを切ってありますのでそれをチェックアウトしてからおためしください。
git clone git@github.com:nabedge/querydsl4-sample.git
git checkout refs/tags/20160313