GithubHelp home page GithubHelp logo

domaframework / doma Goto Github PK

View Code? Open in Web Editor NEW
413.0 27.0 70.0 19.8 MB

DAO oriented database mapping framework for Java 8+

Home Page: https://doma.readthedocs.io/

License: Apache License 2.0

Java 95.82% Kotlin 4.18%
java annotation-processor jdbc dao sql no-dependencies doma kotlin

doma's Introduction

Doma

Doma 2 is a database access framework for Java 8+. Doma has various strengths:

  • Verifies and generates source code at compile time using annotation processing.
  • Provides type-safe Criteria API.
  • Supports Kotlin.
  • Uses SQL templates, called "two-way SQL".
  • Has no dependence on other libraries.

Build Status javadoc project chat Twitter

Examples

Type-safe Criteria API

Written in Java 8:

Entityql entityql = new Entityql(config);
Employee_ e = new Employee_();
Department_ d = new Department_();

List<Employee> list = entityql
    .from(e)
    .innerJoin(d, on -> on.eq(e.departmentId, d.departmentId))
    .where(c -> c.eq(d.departmentName, "SALES"))
    .associate(e, d, (employee, department) -> {
        employee.setDepartment(department);
        department.getEmployeeList().add(employee);
    })
    .fetch();

Written in Kotlin:

val entityql = KEntityql(config)
val e = Employee_()
val d = Department_()

val list = entityql
    .from(e)
    .innerJoin(d) { eq(e.departmentId, d.departmentId) }
    .where { eq(d.departmentName, "SALES") }
    .associate(e, d) { employee, department ->
        employee.department = department
        department.employeeList += employee
    }
    .fetch()

See Criteria API for more information.

SQL templates

Written in Java 15 or above:

@Dao
public interface EmployeeDao {

  @Sql(
    """
    select * from EMPLOYEE where
    /*%if salary != null*/
      SALARY >= /*salary*/9999
    /*%end*/
    """)
  @Select
  List<Employee> selectBySalary(BigDecimal salary);
}

See SQL templates for more information.

More Examples

Try Getting started and simple-examples.

Installing

Gradle

For Java projects:

dependencies {
    implementation("org.seasar.doma:doma-core:2.58.0")
    annotationProcessor("org.seasar.doma:doma-processor:2.58.0")
}

For Kotlin projects, use doma-kotlin instead of doma-core and use kapt in place of annotationProcessor:

dependencies {
    implementation("org.seasar.doma:doma-kotlin:2.58.0")
    kapt("org.seasar.doma:doma-processor:2.58.0")
}

Maven

We recommend using Gradle, but if you want to use Maven, see below.

For Java projects:

...
<properties>
    <doma.version>2.58.0</doma.version>
</properties>
...
<dependencies>
    <dependency>
        <groupId>org.seasar.doma</groupId>
        <artifactId>doma-core</artifactId>
        <version>${doma.version}</version>
    </dependency>
</dependencies>
...
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>1.8</source> <!-- depending on your project -->
                <target>1.8</target> <!-- depending on your project -->
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.seasar.doma</groupId>
                        <artifactId>doma-processor</artifactId>
                        <version>${doma.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

For Kotlin projects, see Kotlin document.

Documentation

https://doma.readthedocs.io/

Chatroom

https://domaframework.zulipchat.com

Related projects

Major versions

Status and Repository

Version Status Repository Branch
Doma 1 stable https://github.com/seasarorg/doma/ master
Doma 2 stable https://github.com/domaframework/doma/ master

Compatibility matrix

Doma 1 Doma 2
Java 6 v
Java 7 v
Java 8 v v
Java 9 v
Java 10 v
Java 11 v
Java 12 v
Java 13 v
Java 14 v
Java 15 v
Java 16 v
Java 17 v
Java 18 v
Java 19 v
Java 20 v
Java 21 v
Java 22 v

Backers & Sponsors

If you use Doma in your project or enterprise and would like to support ongoing development, please consider becoming a backer or a sponsor. Sponsor logos will show up here with a link to your website.

[Become a backer or a sponsor]

doma's People

Contributors

babywkb avatar backpaper0 avatar bakenezumi avatar bufferings avatar chibat avatar dependabot[bot] avatar github-actions[bot] avatar gloryof avatar irof avatar kakusuke avatar lunch2000 avatar lusingander avatar making avatar makotok avatar matsumana avatar momosetkn avatar nakamura-to avatar odashinsuke avatar okurashoichi avatar oohira avatar orekyuu avatar pinzolo avatar raimon49 avatar renovate-bot avatar renovate[bot] avatar rixwwd avatar tasuku-s avatar tom-myz avatar uc4w6c avatar zyyxwada avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

doma's Issues

java.sql.Wrapper を実装する

Doma が提供する JDBC のクラスから委譲先 のインスタンスを取得できるようにする。いざというときに JDBC ドライバ独自の機能にアクセスできるようにするのが目的。

負荷時に [DOMA2015] [DOMA9001] Failed to get a message because of following error : java.util.MissingResourceException が出る

すいません、負荷時に以下のエラーが出ました。
環境は以下となります。
java build 1.8.0_20-b26
Tomcat 8.0.11
doma 2.0.1
MySQL 5.5.40
です。
こちらの作りが良くないのか、doma側なのか分かればいいのですが・・・

ERROR 2015-05-28 21:30:07,053 [ajp-nio-8029-exec-235] org.seasar.doma.jdbc.JdbcException: [DOMA2015] [DOMA9001] Failed to get a message because of following error : java.util.MissingResourceException: Can't find bundle for base name org.seasar.doma.internal.message.MessageResourceBundle, locale ja_JP
        at java.util.ResourceBundle.throwMissingResourceException(ResourceBundle.java:1564)
        at java.util.ResourceBundle.getBundleImpl(ResourceBundle.java:1387)
        at java.util.ResourceBundle.getBundle(ResourceBundle.java:773)
        at org.seasar.doma.message.Message.getSimpleMessageInternal(Message.java:448)
        at org.seasar.doma.message.Message.getMessage(Message.java:435)
        at org.seasar.doma.DomaException.<init>(DomaException.java:62)
        at org.seasar.doma.jdbc.JdbcException.<init>(JdbcException.java:55)
        at org.seasar.doma.internal.jdbc.util.JdbcUtil.getConnection(JdbcUtil.java:42)
        at org.seasar.doma.jdbc.tx.LocalTransaction.lambda$getLocalTransactionContext$171(LocalTransaction.java:245)
        at org.seasar.doma.jdbc.tx.LocalTransaction$$Lambda$9/153667928.get(Unknown Source)
        at org.seasar.doma.jdbc.tx.LocalTransactionContext.getConnection(LocalTransactionContext.java:73)
        at org.seasar.doma.jdbc.tx.LocalTransactionDataSource.getConnectionInternal(LocalTransactionDataSource.java:124)
        at org.seasar.doma.jdbc.tx.LocalTransactionDataSource.getConnection(LocalTransactionDataSource.java:101)
        at org.seasar.doma.internal.jdbc.util.JdbcUtil.getConnection(JdbcUtil.java:40)
        at org.seasar.doma.jdbc.command.SelectCommand.execute(SelectCommand.java:60)
        at jp.xxxxxxxx.dao.ronly.impl.S2SessionDaoImpl.selectBySessionIdAndName(S2SessionDaoImpl.java:77)
        at jp.xxxxxxxx.servlet.ReverseProxyServlet.lambda$processRequest$28(ReverseProxyServlet.java:289)
        at jp.xxxxxxxx.servlet.ReverseProxyServlet$$Lambda$133/1955979288.get(Unknown Source)
        at org.seasar.doma.jdbc.tx.LocalTransactionManager.executeInTransaction(LocalTransactionManager.java:245)
        at org.seasar.doma.jdbc.tx.LocalTransactionManager.requiredInternal(LocalTransactionManager.java:96)
        at org.seasar.doma.jdbc.tx.LocalTransactionManager.required(LocalTransactionManager.java:75)
        at jp.xxxxxxxx.servlet.ReverseProxyServlet.processRequest(ReverseProxyServlet.java:287)
        at jp.xxxxxxxx.servlet.ReverseProxyServlet.doGet(ReverseProxyServlet.java:147)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:618)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.apache.shindig.gadgets.servlet.ETagFilter.doFilter(ETagFilter.java:63)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:504)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:534)
        at org.apache.coyote.ajp.AbstractAjpProcessor.process(AbstractAjpProcessor.java:827)
        at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:658)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1566)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1523)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

        at org.seasar.doma.internal.jdbc.util.JdbcUtil.getConnection(JdbcUtil.java:42)
        at org.seasar.doma.jdbc.tx.LocalTransaction.lambda$getLocalTransactionContext$171(LocalTransaction.java:245)
        at org.seasar.doma.jdbc.tx.LocalTransaction$$Lambda$9/153667928.get(Unknown Source)
        at org.seasar.doma.jdbc.tx.LocalTransactionContext.getConnection(LocalTransactionContext.java:73)
        at org.seasar.doma.jdbc.tx.LocalTransactionDataSource.getConnectionInternal(LocalTransactionDataSource.java:124)
        at org.seasar.doma.jdbc.tx.LocalTransactionDataSource.getConnection(LocalTransactionDataSource.java:101)
        at org.seasar.doma.internal.jdbc.util.JdbcUtil.getConnection(JdbcUtil.java:40)
        at org.seasar.doma.jdbc.command.SelectCommand.execute(SelectCommand.java:60)
        at jp.xxxxxxxx.dao.ronly.impl.S2SessionDaoImpl.selectBySessionIdAndName(S2SessionDaoImpl.java:77)
        at jp.xxxxxxxx.servlet.ReverseProxyServlet.lambda$processRequest$28(ReverseProxyServlet.java:289)
        at jp.xxxxxxxx.servlet.ReverseProxyServlet$$Lambda$133/1955979288.get(Unknown Source)
        at org.seasar.doma.jdbc.tx.LocalTransactionManager.executeInTransaction(LocalTransactionManager.java:245)
        at org.seasar.doma.jdbc.tx.LocalTransactionManager.requiredInternal(LocalTransactionManager.java:96)
        at org.seasar.doma.jdbc.tx.LocalTransactionManager.required(LocalTransactionManager.java:75)
        at jp.xxxxxxxx.servlet.ReverseProxyServlet.processRequest(ReverseProxyServlet.java:287)
        at jp.xxxxxxxx.servlet.ReverseProxyServlet.doGet(ReverseProxyServlet.java:147)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:618)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.apache.shindig.gadgets.servlet.ETagFilter.doFilter(ETagFilter.java:63)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:504)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:534)
        at org.apache.coyote.ajp.AbstractAjpProcessor.process(AbstractAjpProcessor.java:827)
        at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:658)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1566)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1523)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)
Caused by: java.sql.SQLException: Cannot get a connection, general error
        at org.apache.tomcat.dbcp.dbcp2.PoolingDataSource.getConnection(PoolingDataSource.java:130)
        at org.apache.tomcat.dbcp.dbcp2.BasicDataSource.getConnection(BasicDataSource.java:1412)
        at org.seasar.doma.internal.jdbc.util.JdbcUtil.getConnection(JdbcUtil.java:40)
        ... 40 more
Caused by: java.lang.InterruptedException
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.reportInterruptAfterWait(AbstractQueuedSynchronizer.java:2014)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2048)
        at org.apache.tomcat.dbcp.pool2.impl.LinkedBlockingDeque.takeFirst(LinkedBlockingDeque.java:548)
        at org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:437)
        at org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:360)
        at org.apache.tomcat.dbcp.dbcp2.PoolingDataSource.getConnection(PoolingDataSource.java:118)
        ... 42 more

S2SessionDaoImplの 77行目は以下のコードでした。

java.sql.Blob __result = __command.execute();

以上、お手数かけしますがよろしくお願いします。

注釈処理のオプションを設定ファイルに書けるようにしたい

プロジェクトの構成やIDEによっては、自動的に注釈処理のオプションの設定が難しかったりするので、オプションも設定ファイルとして管理できると嬉しいです。

具体的には以下のような状況に陥りました。

  1. IntelliJにて注釈処理を有効にし、doma.domain.convertersdoma.expr.functionsを指定するgradleタスクを書いていた
  2. IntelliJバージョンを上げたところ、設定ファイルの仕様が変わったのか自前実装していた注釈処理の設定を有効にするgradleタスクがエラーを吐くようになった
  3. 暫定的な対処として、gradleタスクの実行をやめ、手作業でオプションを指定するという作業をプロジェクトインポート時に毎回することにした

この状況場合、gradleタスクを直せば終わりという話ではありますが、設定ファイルとしてプロジェクトに組み込めるようになっているほうがよりスマートではないでしょうか。

lombokにはlombok.configを置いておくとそこから注釈処理時にオプションを読み込んでくれる仕組みがあり、実現自体は可能だと思います。
ただ、コードを読む限り、com.sun.tools.javac以下のクラスを利用しており、やるべきではないのかなという気もしますが、いかがでしょうか?

水平分割したテーブルの利用方法

水平分割したテーブルの利用方法

Doma では、データ分散のためにテーブルを水平分割するにはどのような Dao を作ればよいでしょうか。

例えば、1 つのテーブルを同じスキーマのまま books_p1, books_p2, ... books_pX と作っていき、
時間や名前などの一定条件でレコードをいれておくテーブルを決めるものとします。
Dao のメソッドから以下のクエリで分割されたテーブルの番号を指定してアクセスしてみました。

SELECT * FROM books_p/* tableNo */0 WHERE id = /* id */0

ただ、テーブル名に対してパラメーターを埋め込むことはできないようで、以下のようなエラーとなりました。

org.seasar.doma.jdbc.SqlExecutionException: [DOMA2009] SQLの実行に失敗しました。
SQLファイルパス=[META-INF/net/satoyamam/BooksDao/selectById.sql]。
ログ用SQL=[SELECT * FROM books_p1 WHERE id = 1]。
原因は次のものです。com.microsoft.sqlserver.jdbc.SQLServerException: オブジェクト名 &#39;books_p@P0&#39; が無効です。。
根本原因は次のものです。com.microsoft.sqlserver.jdbc.SQLServerException: オブジェクト名 &#39;books_p@P0&#39; が無効です。
    at org.seasar.doma.jdbc.command.SelectCommand.execute(SelectCommand.java:74)
    at net.satoyamam.BooksDaoImpl.selectById(BooksDaoImpl.java:44)
    at net.satoyamam.BookController.index(BookController.java:23)
...

Dao からアクセスするテーブル名を Dao のメソッド引数から動的に変更する場合、
ドキュメントに書かれているような CommandImplementors や QueryImplementors を独自に拡張していくしかないのでしょうか。

また、水平分割されたテーブルを Doma から扱うにあたって、より良い方法はありますでしょうか。

お手すきにて、助言をいただけますと幸いです。

Version number spec is different from the document

Hi,

There's a difference when the version = 0.
Document or implementation should be corrected.

The document says:
http://doma.readthedocs.org/ja/stable/query/insert/#id5

エンティティクラス に @Version が注釈されたプロパティがある場合、 そのプロパティに明示的に 1 以上の値が設定されていればその値を使用します。 もし設定されていないか、 0 以下の値が設定されていれば 1 を自動で設定します。

But actual implementation seems:

if (currentValue == null || currentValue.intValue() < 0) {

        @Override
        public <V extends Number> Boolean visitNumberWrapper(
                NumberWrapper<V> wrapper, Number value, Void q) {
            Number currentValue = wrapper.get();
            if (currentValue == null || currentValue.intValue() < 0) {
                wrapper.set(value);
                return true;
            }
            return false;
        }

Doma version1 has the same issue.

PostgreSQLで、@BatchInsert時にGenerationType.IDENTITYによるID自動生成がされない

概要

@BatchInsert実行時に発行されるINSERT文に、主キーが含まれてしまいます。主キーには0が代入されています。
@insertでは、発行されるINSERT文に主キーが含まれておらず、自動採番がされています。

下記のコードは、GitHubにアップしてあります。
https://github.com/MasatoshiTada/doma-maven

環境

  • Doma 2.12.1
  • PostgreSQL 9.4.2
  • org.postgresql:postgresql:9.4.1211
  • macOS Sierra
  • Eclipse JEE 4.6.1
  • JDK 8u102

テーブル定義

create table employee (
    id serial primary key, -- serial型かつデフォルト値なし
    name varchar(100),
    joined_date date
);

エンティティクラス

@Entity
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public int id;

    public String name;

    public LocalDate joinedDate;
}

Daoクラス

@Dao(config = PostgresConfig.class)
public interface PostgresEmployeeDao {
    @Insert
    int insert(Employee employee);

    @BatchInsert(batchSize = 10, exclude = {"id"})
    int[] batchInsert(List<Employee> employeeList);
}

テストコード

public class PostgresEmployeeDaoTest {
    /**
     * このテストは成功する
     */
    @Test
    public void 社員を1件追加できる() {
        Employee employee = new Employee("太郎", LocalDate.of(2016, 10, 10));
        int rows = txManager.required(() -> {
            return employeeDao.insert(employee);
        });
        assertThat(rows, is(1));
    }

    /**
     * このテストが失敗する
     */
    @Test
    public void 社員を3括追加できる() {
        List<Employee> employeeList = Arrays.asList(
                new Employee("太郎", LocalDate.of(2016, 10, 10)),
                new Employee("次郎", LocalDate.of(2016, 10, 11)),
                new Employee("三郎", LocalDate.of(2016, 10, 12)));
        int[] rowsList = txManager.required(() -> {
            return employeeDao.batchInsert(employeeList);
        });
        boolean allOne = Arrays.stream(rowsList).allMatch(rows -> rows == 1);
        assertThat(allOne, is(true));
        assertThat(rowsList.length, is(3));
    }

}

実行ログ

org.seasar.doma.jdbc.BatchUniqueConstraintException: [DOMA2029] 一意制約違反によりバッチ更新処理が失敗しました。
SQLファイルパス=[null]。
実際のSQL=[insert into employee (id, name, joined_date) values (?, ?, ?)]。
詳しい原因は次のものです。java.sql.BatchUpdateException: Batch entry 1 insert into employee (id, name, joined_date) values (0, '次郎', '2016-10-11 +09'::date) was aborted: ERROR: duplicate key value violates unique constraint "employee_pkey"
  詳細: Key (id)=(0) already exists.  Call getNextException to see other errors in the batch.
    at org.seasar.doma.jdbc.command.BatchModifyCommand.executeBatch(BatchModifyCommand.java:132)
    at org.seasar.doma.jdbc.command.BatchModifyCommand.executeBatch(BatchModifyCommand.java:114)
    at org.seasar.doma.jdbc.command.BatchInsertCommand.executeInternal(BatchInsertCommand.java:41)
    at org.seasar.doma.jdbc.command.BatchModifyCommand.execute(BatchModifyCommand.java:67)
    at com.example.dao.PostgresEmployeeDaoImpl.batchInsert(PostgresEmployeeDaoImpl.java:165)
    at com.example.dao.PostgresEmployeeDaoTest.lambda$2(PostgresEmployeeDaoTest.java:57)
    at org.seasar.doma.jdbc.tx.LocalTransactionManager.executeInTransaction(LocalTransactionManager.java:245)
    at org.seasar.doma.jdbc.tx.LocalTransactionManager.requiredInternal(LocalTransactionManager.java:96)
    at org.seasar.doma.jdbc.tx.LocalTransactionManager.required(LocalTransactionManager.java:75)
    at com.example.dao.PostgresEmployeeDaoTest.社員を3件一括追加できる(PostgresEmployeeDaoTest.java:56)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: java.sql.BatchUpdateException: Batch entry 1 insert into employee (id, name, joined_date) values (0, '次郎', '2016-10-11 +09'::date) was aborted: ERROR: duplicate key value violates unique constraint "employee_pkey"
  詳細: Key (id)=(0) already exists.  Call getNextException to see other errors in the batch.
    at org.postgresql.jdbc.BatchResultHandler.handleError(BatchResultHandler.java:151)
    at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2159)
    at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:463)
    at org.postgresql.jdbc.PgStatement.executeBatch(PgStatement.java:794)
    at org.postgresql.jdbc.PgPreparedStatement.executeBatch(PgPreparedStatement.java:1662)
    at org.seasar.doma.jdbc.command.BatchModifyCommand.executeBatch(BatchModifyCommand.java:127)
    ... 33 more
Caused by: org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "employee_pkey"
  詳細: Key (id)=(0) already exists.
    at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2458)
    at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2158)
    ... 37 more

【質問】doma2 クエリビルダーのIn句のパラメータ(List)指定方法

doma2クエリビルダーのIn句

上記のリンクと全く同じ質問です。

Domaの動的SQLを作成するため、SelectBuilderを使い、SQLを作成しました。
ただし、In句にリストの指定方法がわかりません。

試した、ソースは以下になります。

SelectBuilder builder = SelectBuilder.newInstance(config);
builder.sql("select * from user where id in (").param(List.class,Arrays.asList(1,2,3)).sql(")");
builder.getEntityResultList(User.class);

を実行すると、以下のエラーが発生します。

Caused by: org.seasar.doma.internal.jdbc.scalar.ScalarException: [DOMA1007] 型[java.util.List]の値[[1, 2, 3]]に対応するラッパークラスが見つかりません。

domaのバージョンは2.13.0になります。

パラメータにリスト形(もしくは配列)を指定する方法を教えてください。
よろしくお願いします。

DB(UTC)/Server(JST)の環境でLocalDateで指定したカラムの値がJST→UTCのタイムゾーン影響を受けてDBにインサートされる

事象

利用しているバージョン

  • doma 2.16.1
  • kotlin 1.1.2-2

確認した事象

次のタイムゾーン環境で確認。

  • DB:UTC
  • Server:JST

次のようなdata classでエンティティを作成。

@Entity(immutable = true)
@Table(name = "tasks")
data class Task(
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "id")
        val id: Int? = null,
        @Column(name = "title")
        val title: String,
        @Column(name = "finished_at")
        val finishedAt: LocalDate,
        @Column(name = "created_at")
        val createdAt: LocalDateTime,
        @Column(name = "updated_at")
        val updatedAt: LocalDateTime
)

finishedAtには LocalDateの型を指定。

Daoのテストを行い tasksテーブルにデータをインサートした後に finishedAtの値を検証する。

@Test
fun insert() {
    val now = LocalDateTime.now()
    val finishedAt = LocalDate.of(2017, 8, 1)

    domaConfig.getTransactionManager().required {
        val data = Task( null, "title",finishedAt,now,now)
        val inserted = target.insert(data)
        assertThat(inserted.count, `is`(1))

        val actual = target.selectById(inserted.entity.id?.toLong())
        assertThat(actual.id, `is`(inserted.entity.id))
        // finishedAt = '2017/8/1'でinsertしたがDBの値はfinishedAt = '2017/7/31'である
        assertThat(actual.finishedAt, `is`(LocalDate.of(2017, 7, 31)))
    }
}

finishedAtカラムの値は2017-08-01を期待していたが、2017-07-31となっている。
これはServer(JST)からDB(UTC)に値が渡る過程でマイナス9時間した結果2017-07-31と予想する。

確認したログ

domaのログでは次のように出力されている。

8 07, 2017 11:22:26 午前 org.seasar.doma.jdbc.tx.LocalTransaction begin
情報: [DOMA2063] ローカルトランザクション[1513608173]を開始しました。
8 07, 2017 11:22:26 午前 app.dao.TaskDaoImpl insert
情報: [DOMA2220] ENTER  : クラス=[app.dao.TaskDaoImpl], メソッド=[insert]
Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
11:22:26.815 [main] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Fetching JDBC Connection from DataSource
Mon Aug 07 11:22:26 JST 2017 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Mon Aug 07 11:22:27 JST 2017 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
8 07, 2017 11:22:27 午前 app.dao.TaskDaoImpl insert
情報: [DOMA2076] SQLログ : SQLファイル=[null],
insert into tasks (title, finished_at, created_at, updated_at) values ('title', '2017-08-01', '2017-08-07 11:22:26.644', '2017-08-07 11:22:26.644')
8 07, 2017 11:22:27 午前 app.dao.TaskDaoImpl insert
情報: [DOMA2221] EXIT   : クラス=[app.dao.TaskDaoImpl], メソッド=[insert]

mysqlのクエリログでは次のように出力されている。

2017-08-07T02:22:24.784502Z	   51 Query	insert into tasks (title, finished_at, created_at, updated_at) values ('title', '2017-07-31', '2017-08-07 02:22:26.644', '2017-08-07 02:22:26.644')

確認したいこと

LocalDateはタイムゾーンの影響を受けず値を保持する認識でおりましたが、DBにデータが入る前に変換を行っているのでしょうか?
こちらはバグになりますでしょうか?

事象を再現できるソース

次のレポジトリに上記事象を再現できるソースを置きました。
合わせてご確認ください。

https://github.com/nsoushi/doma-kotlin

SQLコメントでenumを利用した場合に、accessorMethodを参照するようにして欲しい

SQLコメントでenumを利用した場合に、accessorMethodを参照するようにして欲しい。

@Domain
public enum Foo{
    HOGE(1),BAR(2);
    private Integer id;

    private Foo(Integer id){
        this.id = id;
    }

    public Integer getValue(){
        return id;
    }
}

上記のようなドメインクラスがある場合に、
SQLコメントでこのenumを指定した場合、accessorMethodを使って getValue() を呼んで欲しいです。
以下のようなイメージです。

select
 *
from
 hogehoge
where
 foo_id = /* @HOGE */1   <= 現状ですと @HOGE.getValue() と書く必要がある

以上、よろしくお願いします。

イミュータブルなエンティティを返す SELECT でフィールドに対応するカラムが存在しない場合、NullPointerException が発生する

ensureResultMapping=true にすると、ResultMappingException になりますが、false の場合は、_Employee#newEntity で NullPointerException が発生します。

再現コード

@Dao
public interface EmployeeDao {
    @Select
    List<Employee> findAllExcludeNote();
}
select id, name from Empoyee -- note 列を取得しない
@Entity(immutable = true)
public class Employee {
    final Integer id;
    final String name;
    final String note;

    public Employee(Integer id, String name, String note) {
        this.id = id;
        this.name = name;
        this.note = note;
    }
}

カラムリスト展開コメント で DOMA4257 エラー

1件検索を行うメソッドの戻り値を Optional にした場合に、
SQL ファイルでカラムリスト展開コメントを利用すると、DOMA4257 エラーが発生する。
再現コード

@Dao
public interface HogeDao {
  @Select
  Optional<Hoge> findBy(Long id);
}
select /*%expand*/* from HOGE where ID = /*id*/1

Configの設定の一部が無視される

UnknownColumnHandlerTransactionManager の設定が RuntimeConfig にコピーされていないためデフォルトのインスタンスが使われしまう。

Kotlinでのエンティティクラスについて

Kotlin+domaではエンティティクラスで「継承は使わない」とあります。
created_at, update_at, versionのようなすべてのテーブルに共通的に付加したいフィールドに対して継承を使った経験がありましたが(javaですが)、こういうことはできないということですね?
またエンベッダブルクラスを使うといいかと思いましたが、「楽観的排他制御用のバージョンを定義できません。」とのことなので、これもできないということですね?

populate を使ったメソッドで DOMA4122 が出る‏

Doma 2.3.1
ドキュメント通り populate を試してみましたが、

[DOMA4122] SQLファイル[META-INF/hoge/HogeDao/update.sql]の妥当検査に失敗しました。メソッドのパラメータ[entity]がSQLファイルで参照されていません。

が出ます。こちらの環境の問題でしょうか?
ソース

@Update(sqlFile=true)
int update(VerTable entity, String condName);
update VerTable set /*%populate*/Id = Id where Name = /*condName*/'a'

UnknownColumnHandler の handle() を空実装にすると NullPointerException が発生する

未知のカラムが検出された場合にマッピングはせずに無視するようにするため、UnknownColumnHandler を handle() 内で何もしないように実装したところ、EntityProvider.build() 内で NullPointerException が発生しました。

UnknownColumnHandler の handle() を空実装とすることは過去に勧められているようなのですが、現在もこの方針で実装することは問題ないのでしょうか。
https://twitter.com/nakamura_to/status/443416808201003008

例外のスタックトレース

java.lang.NullPointerException
    at org.seasar.doma.internal.jdbc.command.EntityProvider.build(EntityProvider.java:90)
    at org.seasar.doma.internal.jdbc.command.EntityProvider.get(EntityProvider.java:77)
    at org.seasar.doma.internal.jdbc.command.ResultSetIterator.next(ResultSetIterator.java:69)
    at org.seasar.doma.internal.jdbc.command.AbstractIterationHandler.iterate(AbstractIterationHandler.java:78)
    at org.seasar.doma.internal.jdbc.command.AbstractIterationHandler.handle(AbstractIterationHandler.java:66)  
    at org.seasar.doma.internal.jdbc.command.AbstractSingleResultHandler.handle(AbstractSingleResultHandler.java:53)
    at org.seasar.doma.jdbc.command.SelectCommand.handleResultSet(SelectCommand.java:122)
    at org.seasar.doma.jdbc.command.SelectCommand.executeQuery(SelectCommand.java:114)
    at org.seasar.doma.jdbc.command.SelectCommand.execute(SelectCommand.java:69)
    at com.example.myapp.dao.ProductsDaoImpl.selectById(ProductsDaoImpl.java:43)
(以下略)

UnknownColumnHandler の実装

public class DomaUnknownColumnHandler implements UnknownColumnHandler {
    @Override
    public void handle(Query query, EntityType<?> entityType, String unknownColumnName) {
        //    例外を投げないxx   
    }
}

お手すきにて、助言をいただけますと幸いです。

HELP

How to use the same set of Entity, how to meet the MySQL and Oracle's primary key generation strategy

Daoインターフェイスの型パラメータ定義に関しての質問

概要

現在Daoインターフェイスに型パラメータを定義した場合、下記のようなアサートが行われていると思います。

[DOMA4059] Daoインタフェースには型パラメータを定義できません。 at com.example.BaseDao

ソースを読み切れていないので質問になってしまうのですが、こちらのアサートはDaoの実装を生成する上では止む終えない処理になるのでしょうか?
また、こちらの回避する方法はないでしょうか?

背景

現在、Domaを使用させてもらう上で汎用的なDaoインターフェイスの定義をできないか検討しています。
その中で、デフォルトメソッドと併用することで以下のような処理を考えていました。

// 汎用的なDaoインターフェイス
@Dao
interface BaseDao {
    default <T> List<T> findAll(Class<T> clazz) {
        Config config = Config.get(this);
        SelectBuilder builder = SelectBuilder.newInstance(config);
        EntityType<T> entityType = EntityTypeFactory.getEntityType(clazz, config.getClassHelper());
        builder.sql("SELECT * FROM " + entityType.getTableName());
        return builder.getEntityResultList(clazz);
    }
}

// エンティティ単位のインターフェイス
@Dao
interface MessageDao extends BaseDao {}

// 使用
class Main {
    void sample() {
        MessageDao dao = ...; // 何かしらの方法で初期化
        List<Message> messages = dao.selectAll(Message.class);
    }
}
// * サンプルコードのため詳細な定義やバリデーションなどは省略

こちらのアプローチで実現は出来そうなのですが、この方法だと必ず引数にClassやインスタンスを渡さなければならないため、もう少しシグネチャを整理できないかと考えていました。

具体的にはこちらで紹介されている型パラメータの定義によるアプローチを利用できればと考えています。
http://d.hatena.ne.jp/Nagise/20131121/1385046248 *1

// 型パラメータ定義を追加
@Dao
interface BaseDao<T> {    
    // 引数のClassとメソッドに対する型パラメータ定義が省略可能
    default List<T> findAll() {
        Class<T> clazz = ..; // *1 の方法で型パラメータからClassを生成

        // 以下の処理は変更前と同様
        Config config = Config.get(this);
        SelectBuilder builder = SelectBuilder.newInstance(config);
        EntityType<T> entityType = EntityTypeFactory.getEntityType(clazz, config.getClassHelper());
        builder.sql("SELECT * FROM " + entityType.getTableName());
        return builder.getEntityResultList(clazz);
    }
}

// interfaceの継承時にEntityとなるクラスの指定
@Dao
interface MessageDao extends BaseDao<Message> {}

// 使用
class Main {
    void sample() {
        MessageDao dao = ...;
        List<Message> messages = dao.selectAll();  // 引数が不要になる
    }
}

これが可能になれば、ユースケースに合わせ汎用インターフェイスの拡張もしやすそうな気がします。

// 全Dao共通のDao
@Dao
interface BaseDao<T> {
    default <T> List<T> findAll() {
        // ...
    }
    default int count() {
        // ...
    }
}
// 単一主キー用Dao
@Dao
interface SingleIdDao<T, ID> extends BaseDao {
    default List<T> findById(ID id) {
        // ...
    }
}
// 複合主キー用Dao
@Dao
interface TwinIdDao<T, ID1, ID2> extends BaseDao {
    default List<T> findById(ID1 id1, ID2 id2) {
        // ...
    }
}

Domaの設計**として、SELECT系のSQL構築は行わないというのがあるのは承知しており、私もその**には賛同しております。

しかし、潜在的なニーズとして基本となるSQLを構築して欲しいというケースもあるように思い、ユーザが許容する範囲内で自前でJavaコードによる汎用SQL生成の手段として使用できないかと質問させてもらいました。

お手すきのときに、確認して頂けましたら幸いです。

SQL Server クエリヒントを使ったクエリが、ページング処理で正しく動かない問題

SelectOptions#offset を指定した場合、例外が発生する。

以下サンプル

階層構造を持つテーブルに対し、再帰クエリにより階層順にデータを取得する。
その際、再帰の上限回数を指定するクエリヒントを利用している。

テーブル

Table_1

id name parent_id
1 aaa null
2 bbb 1
3 ccc 1
4 ddd 2
5 eee 3
6 fff 2
7 ggg 4

Dao

@Select
List<String> findPathPaging(SelectOptions options);
with cte (id, lvl, lvl_path) as (
  select id, 1 as lvl, cast(name as nvarchar(4000)) as lvl_path
  from Table_1
  where parent_id is null
  union all
  select Table_1.id, lvl + 1, concat(lvl_path, '-', Table_1.name)
  from Table_1 inner join cte on Table_1.parent_id = cte.id
)
select lvl_path from cte order by lvl_path option (maxrecursion 3)

offset を指定しない場合は正しく動く。

// 指定無し
// [aaa, aaa-bbb, aaa-ccc, aaa-bbb-ddd, aaa-ccc-eee, aaa-bbb-fff, aaa-bbb-ddd-ggg]
List<String> results = dao.findPathPaging(SelectOptions.get());
// limit のみ指定
// [aaa, aaa-bbb]
result = dao.findPathPaging(SelectOptions.get().limit(2));

しかし、offset を指定した場合には、例外が発生する。

// offset 指定
// org.seasar.doma.jdbc.SqlExecutionException: [DOMA2009] SQLの実行に失敗しました。
// 根本原因は次のものです。com.microsoft.sqlserver.jdbc.SQLServerException: キーワード 'option' 付近に不適切な構文があります。
results = dao.findPathPaging(SelectOptions.get().offset(1));

Doma のログより発行された SQL を抜粋

with cte (id, lvl, lvl_path) as (
  select id, 1 as lvl, cast(name as nvarchar(4000)) as lvl_path
  from Table_7
  where parent_id is null
  union all
  select Table_7.id, lvl + 1, concat(lvl_path, '-', Table_7.name)
  from Table_7 inner join cte on Table_7.parent_id = cte.id
)
select * from ( select temp_.*, row_number() over( order by lvl_path option (maxrecursion 3) ) as doma_rownumber_ from ( select lvl_path from cte ) as temp_ ) as temp2_ where doma_rownumber_ > 1

以下の SQL になって欲しい。

with cte (id, lvl, lvl_path) as (
  select id, 1 as lvl, cast(name as nvarchar(4000)) as lvl_path
  from Table_7
  where parent_id is null
  union all
  select Table_7.id, lvl + 1, concat(lvl_path, '-', Table_7.name)
  from Table_7 inner join cte on Table_7.parent_id = cte.id
)
select * from ( select temp_.*, row_number() over( order by lvl_path ) as doma_rownumber_ from ( select lvl_path from cte ) as temp_ ) as temp2_ where doma_rownumber_ > 1
option (maxrecursion 3)

これは、OPTION 句 以外に、FOR 句 でも同様の例外が発生します。
ドキュメントによると SQL Server の SELECT では、ORDER BY の後に FOR/OPTION が来る可能性があるようです。

InsertしたレコードのEntityが欲しいです

質問です。

具体的に言うと、親子関係があるテーブルの親を先にINSERTしてそのIDを子のINSERTに使うので、INSERTしたレコードのEntityを戻り値として受け取りたいです。

ドキュメントにあるorg.seasar.doma.Resultが今使っているバージョン(2.6.0)には含まれていないのですが、2.6.0だとどうすれば良いでしょうか?

それと、戻り値のクラスはイミュータブルなエンティティである必要があるみたいですが、
エンティティクラスはDoma-Genで生成しています。出来れば戻り値にそのエンティティを使いたいです。
良い方法はないでしょうか?

カラム数が多いテーブルを使用した際に「Method code too large!」エラーが発生する

300カラムを超えるテーブルに対してEntityクラスを定義した場合、アプリケーション実行時に、生成されるコンストラクタでエラーが発生します。

Caused by: java.lang.RuntimeException: Method code too large!
	at sl.org.objectweb.asm.MethodWriter.a(Unknown Source)
	at sl.org.objectweb.asm.ClassWriter.toByteArray(Unknown Source)
	at org.springsource.loaded.ReloadableType$MergedRewrite$ChainedAdapters.getBytes(ReloadableType.java:984)
	at org.springsource.loaded.ReloadableType$MergedRewrite.rewrite(ReloadableType.java:962)
	at org.springsource.loaded.ReloadableType.rewriteCallSitesAndDefine(ReloadableType.java:934)
	at org.springsource.loaded.ReloadableType.(ReloadableType.java:173)
	at org.springsource.loaded.TypeRegistry.addType(TypeRegistry.java:1103)
	at org.springsource.loaded.agent.SpringLoadedPreProcessor.preProcess(SpringLoadedPreProcessor.java:335)
	at org.springsource.loaded.agent.ClassPreProcessorAgentAdapter.transform(ClassPreProcessorAgentAdapter.java:107)
	... 119 more

生成されたコンストラクタ

    private _Entity() {
        ...
        __list.add($column1);
        __map.put("column1", $column1);
        __list.add($column2);
        __map.put("column2", $column2);
        ...(カラム数が多すぎてエラーになっている模様)

カラム数を減らす(テーブル定義の変更をする)ことなく回避する方法はありますでしょうか?

  • Java 1.8
  • Doma 2.6.0
  • Spring Boot 1.3.6.RELEASE
  • MySQL 5.6

不足情報などあればご指摘ください。
恐れ入りますが何卒よろしくお願いいたします。

PostgresDialect で @Table(quote=true), GenerationType.IDENTITY のEntityでID生成エラーが発生する

内容

PostgreSQL を操作するために、 Entity に @Table(quote = true) を指定していて、 @Id @GeneratedValue(strategy = GenerationType.IDENTITY) Long id; なプロパティを定義している場合に、IDプロパティの生成に失敗する。

@Entity(listener = TableNameListener.class, naming = NamingType.SNAKE_LOWER_CASE)
@Table(name = "table_name", quote = true)
public class TableName {

    /** ID */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    Long id;

PostgreSQL で 自動採番を定義する際に SERIAL 型を指定している。この場合、 "table_name_column_seq" というシーケンスが生成されます。

@Table(quote=true) が指定されていると、 PostgresDialect#getIdentitySelectSql(String, String)qualifiedTableName へクオートされたテーブル名 ( "table_name" ) が渡されるようです。

PostgreSQL で予約後になっているキーワード ( "user" や "password" , "group" など ) をテーブル名やカラム名に利用する場合、または大文字小文字を考慮した実装だと思います。

Returning rows in PostgreSQL with a table called "user" - Database Administrators Stack Exchange

しかし、 getIdentitySelectSql 内部では、シーケンスから採番するために以下のSQLを生成します。

select currval('table_name_column_seq');

この処理でテーブル名がダブルクオートで囲われる場合を考慮できていないようです。その結果

select currval('"table_name"_column_seq');

のようなSQLが生成されていました。

環境

  • java version "1.8.0_31"
  • org.seasar.doma:doma:2.2.0
  • org.postgresql:postgresql:9.3-1103-jdbc41
  • PostgreSQL 9.3

エラーログ

org.seasar.doma.jdbc.JdbcException: [DOMA2018] エンティティ[User]のIDプロパティの生成に失敗しました。原因は次のものです。org.postgresql.util.PSQLException: ERROR: 名前構文が無効です
  ポジション: 16
    at org.seasar.doma.jdbc.id.AbstractIdGenerator.getGeneratedValue(AbstractIdGenerator.java:61)
    at org.seasar.doma.jdbc.id.BuiltinIdentityIdGenerator.getGeneratedValue(BuiltinIdentityIdGenerator.java:101)
    at org.seasar.doma.jdbc.id.BuiltinIdentityIdGenerator.generatePostInsert(BuiltinIdentityIdGenerator.java:62)
    at org.seasar.doma.jdbc.entity.GeneratedIdPropertyType.lambda$postInsert$9(GeneratedIdPropertyType.java:203)
    at org.seasar.doma.jdbc.entity.GeneratedIdPropertyType$$Lambda$65/921500150.get(Unknown Source)
    at org.seasar.doma.jdbc.entity.GeneratedIdPropertyType$ValueSetter.visitNumberWrapper(GeneratedIdPropertyType.java:233)
    at org.seasar.doma.jdbc.entity.GeneratedIdPropertyType$ValueSetter.visitNumberWrapper(GeneratedIdPropertyType.java:223)
    at org.seasar.doma.wrapper.NumberWrapperVisitor.visitLongWrapper(NumberWrapperVisitor.java:60)
    at org.seasar.doma.wrapper.LongWrapper.accept(LongWrapper.java:78)
    at org.seasar.doma.jdbc.entity.DefaultPropertyType.modifyIfNecessary(DefaultPropertyType.java:335)
    at org.seasar.doma.jdbc.entity.GeneratedIdPropertyType.setIfNecessary(GeneratedIdPropertyType.java:219)
    at org.seasar.doma.jdbc.entity.GeneratedIdPropertyType.postInsert(GeneratedIdPropertyType.java:202)
    at org.seasar.doma.jdbc.query.AutoInsertQuery.generateId(AutoInsertQuery.java:177)
    at org.seasar.doma.jdbc.command.InsertCommand.executeInternal(InsertCommand.java:37)

SQL ファイルを使用する update が実行されない (Doma 2.3)

2.2 から 2.3 にバージョンを上げたところ、第一引数が Entity では無い、SQL ファイルを利用する update が SKIP されるようになりました。

情報: [DOMA2223] SKIP   : クラス=[hoge.dao.HogeDaoImpl], メソッド=[updateHoge], 理由=[STATE_UNCHANGED]

update のメソッドの第一引数が Entity のものは、「更新カラムリスト生成コメント」を使用しない場合でも実行されています。

SelectBuilder で ' の中に param を設定するとパラメータが設定されない

  • Doma Version: 2.10.0
  • OS Version: Windows 10 Pro x64
  • Database: PostgreSQL 9.5 x64

SelectBuilder で ' の中に param を設定すると,パラメータが設定されません。

コード

SelectBuilder sb = SelectBuilder.newInstance(config);
sb.sql("select '").param(String.class, "bar");
sb.sql("'");
System.out.println(sb.getSql().getFormattedSql());

実行結果

select '/*p1*/0
'

期待する結果

select 'bar'
または
select '''bar'''

param の用途が不適切と思われますが,ご確認をよろしくお願いします。
※SQL Injection のサニタイジングを期待して使用しています

SQLの埋め込み変数コメントの拡張希望

いつも利用させてもらっています。
ありがとうございます。

SQLの埋め込み変数コメントの機能につきまして、1点拡張のお願いになります。

String code = "abc";

とあったとき、

select ...
from ...
where code = '/*# code */'

とSQLを書くと期待したwhere code = 'abc'ではなく、where code = '/*# code */'
と現在の仕様では展開されるようです。

こちらをwhere code = 'abc'になるよう拡張していただくことは可能でしょうか。

といいますのが、codeの値により異なる実行計画が選択されるケースで、プリペアしたくない場合があるからです。
旧S2DAOでは埋め込みコメント/*$引数名*/によりステートメントを明示的に分けることで対処しておりました。

インジェクションのリスクがややあがる懸念はあるかと思いますが、禁則文字のチェックは評価後に行われているようなので問題はないのでは、と考えます。

ご検討のほどよろしくお願い致します。

バッチ挿入のパフォーマンスが悪い

PostgreSQLにて、id列に@GeneratedValue(strategy = GenerationType.IDENTITY)
をつけたエンティティのバッチ挿入を行う場合、
idをエンティティにセットしなければならない関係上preparedStatementを使いまわしてくれず、
かつ1件挿入のたびにidを取得しにいってしまうようです。

バッチ挿入のユースケースでは、idのセットは必要でないことが多いため、
idセットを行うかどうかをDaoで制御できればよいなと思いました。

加えて、事前にidを取得できるDBであれば、
使うidを事前に予約しておくことでSQL発行回数を減らせると考えています。

例えばPostgreSQLであれば

select nextval('hoge_id_seq') from generate_series(1, 1000)

のように取得できます

SQLテンプレートで組み立てたSQLを別のSQLに組み込んで実行したい

例えばAmazon RedshiftのUNLOADコマンドのselect-statement部分をDomaのSQLテンプレートで表現し、UNLOADコマンドを実行したい。

https://gist.github.com/newta/03584d39116a362efde0d72ee83e68f1 のコードと同等のことを実現する場合は、次のようにすれば良いと思われる。

@ HandleSqlアノテーションをDomaで新規に実装し、組み立てたSQLをアプリで受け取って任意に利用できるようにする。

@Dao(config = AppConfig.class)
public interface MyDao {

  @HandleSql
  void unload(LocalDateTime startAt, LocalDateTime endAt, BiFunction<Config, SqlText, Void> handler); 
}

SQLファイルは次のようなものを用意する。

SELECT 
  hoge,foo,bar
FROM
  data_log_table
WHERE
  created_at BETWEEN /*^ startAt */'2016-11-01 10:00:00' AND /*^ endAt */'2016-11-01 11:00:00'

利用するコードは次のようなものになる。

S3BucketPath s3bucket = ...
CustomAWSCredentials credenials = ...
MyDao dao = ...
dao.unload(startAt, endAt, (config, sqlText) -> {
  String sql = String.format("UNLOAD ('%s') to '%s' credentials 'aws_access_key_id=%s;aws_secret_access_key=%s' parallel off",
                        sqlText.getRawSql(), s3bucket.buildPath(), credenials.getAccessKey(), credenials.getSercretKey());

  DataSource dataSource = config.getDataSource();
  Connection connection = dataSource.getConnection();
  PreparedStatement ps = connection.preparedStatement(sql);
  ps.executeUpdate();
});

上のコードは冗長だがUnloadCommnadのようなクラスをアプリで用意してもらえれば簡潔に書ける。

S3BucketPath s3bucket = ...
CustomAWSCredentials credenials = ...
UnloadCommnad command = new UnloadCommnad(s3bucket, credenials);
MyDao dao = ...
dao.unload(startAt, endAt, command);

埋め込み変数コメントの展開後にスペースを挿入しないで欲しい

お世話になっております。
千葉です。

埋め込み変数コメントが展開された後にスペースが挿入されるようなのですが、挿入されないように修正頂けないでしょうか?

SQLファイルが以下の場合、

select * from /*#envPrefix*/SCHEMA.TABLE

envPrefix="STG_" が展開されると以下のようになります。

select * from STG_ SCHEMA.TABLE

期待する結果としては以下です。

select * from STG_SCHEMA.TABLE

以上、ご検討のほど宜しくお願い致します。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.