org.seasar.doma.jdbc.JdbcException: [DOMA2022] IDプロパティのないエンティティ[LogicalDeletable]の更新や削除はできません
-
-
Save gakuzzzz/4372720 to your computer and use it in GitHub Desktop.
import org.joda.time.DateTime; | |
import org.seasar.doma.Column; | |
import org.seasar.doma.Entity; | |
@Entity | |
public abstract class LogicalDeletable { | |
@Column | |
protected DateTime deletedTime; | |
} |
import javax.annotation.Nonnull; | |
import org.seasar.doma.Column; | |
import org.seasar.doma.Entity; | |
import org.seasar.doma.GeneratedValue; | |
import org.seasar.doma.GenerationType; | |
import org.seasar.doma.Id; | |
import org.seasar.doma.SequenceGenerator; | |
import org.seasar.doma.jdbc.entity.NamingType; | |
@Entity(naming = NamingType.SNAKE_UPPER_CASE) | |
public class Employee extends LogicalDeletable { | |
@Id | |
// @GeneratedValue(strategy = GenerationType.SEQUENCE) // Domainオブジェクトに@GeneratedValueつけようとすると怒られる | |
// @SequenceGenerator(sequence = "EMPLOYEE_SEQ") | |
public EmployeeId id; | |
@Column | |
public String name; | |
} |
import org.seasar.doma.Dao; | |
import org.seasar.doma.Delegate; | |
import org.seasar.doma.Update; | |
@Dao | |
public interface LogicalDeletableDao { | |
@Update | |
int update(LogicalDeletable entity); | |
@Delegate(to = LogicalDeletableDaoDelegate.class) | |
int delete(LogicalDeletable entity); | |
} |
import org.joda.time.DateTime; | |
import org.seasar.doma.jdbc.Config; | |
public class LogicalDeletableDaoDelegate { | |
private final Config config; | |
private final LogicalDeletableDao dao; | |
public LogicalDeletableDaoDelegate(final Config config, final LogicalDeletableDao dao) { | |
this.config = config; | |
this.dao = dao; | |
} | |
public int delete(final LogicalDeletable entity) { | |
entity.deletedTime = DateTime.now(); | |
return dao.update(entity); | |
} | |
} |
import org.seasar.doma.Dao; | |
import org.seasar.doma.Delegate; | |
import org.seasar.doma.Insert; | |
import org.seasar.doma.Select; | |
import org.seasar.doma.Update; | |
@Dao | |
public interface EmployeeDao extends LogicalDeletableDao { | |
@Update | |
int update(Employee entity); | |
@Insert | |
int insert(Employee entity); | |
@Delegate(to = LogicalDeletableDaoDelegate.class) | |
int delete(Employee entity); | |
} |
LogicalDeletableDao
を 以下のようにすればいけるかと思ったが、
@Dao
public interface LogicalDeletableDao<T extends LogicalDeletableEntity> {
@Update
int update(T entity);
@Delegate(to = LogicalDeletableDaoDelegate.class)
int delete(T entity);
}
[DOMA4059] Daoインタフェースには型パラメータを定義できません。
というエラーで怒られる。
かといって@Dao
を付けないと
[DOMA4188] @Daoが注釈されたインタフェースは@Daoが注釈されてないインタフェース[models.shared.LogicalDeletableDao]をextendsできません。
となる。
余談だが、Domain Object型のプロパティに @GeneratedValue
を付けようとすると怒られる。
@Id
に Domain Object型が使えなくて非常につらいです……。
なるほど。
現状って EntityListener
は 1 Entity
につき 1クラスのみ指定できる感じですよね?
実はこれとは別に updateBy
を更新する EntityListener
を用意してるんですよね。
その実装と混ぜてもいいんですが、論理削除じゃない Entity
も存在していて、そいつらのupdatedBy
も更新してやる必要があるという……。
なんか厄介な設計で申し訳なくなってきました><
現状って EntityListener は 1 Entity につき 1クラスのみ指定できる感じですよね?
そうですね。1 Entity につき 1クラスです。
1つ上の例で示したコードでは、メソッド名で条件分岐させていますが、論理削除用のアノテーションを独自に作ってDaoのメソッドに注釈すれば、論理削除する処理としない処理が混在していても明確に区別できるのではないかなーと思います。
上の例で言うところのLogicalDeletableListenerを継承するEntityListenerと、直接Domaのインタフェースを実装するEntityListenerで分けてしまうのもありだと思います。
あれこれ考えてみましたが、ご提案頂いた PreUpdateContext
がメソッドの情報を返してくれる形がやっぱり一番良さそうです。
EntityListener
の継承に若干迷いがありましたが、よくよく考えるとそこまで強く否定する材料もなかったです。
お手すきの際にでも getDaoMethod()
の実装を入れて頂けますでしょうか?
色々わがままを申してお手数をお掛けしますが、よろしくお願いしますm(_ _)m
了解です。
EntityListener周辺の機能拡張で進めます。
色々わがままを
こちらとしては、いろいろ意見を聞けておもしろいのでお気になさらず。
対応してみました。このSNAPSHOTでお試しください。
EntityListenerのcontextからメソッドやConfigを取れるようにしました。こんな感じで取得できます。
public class EmployeeListener implements EntityListener<Employee> {
@Override
public void preUpdate(Employee employee, PreUpdateContext context) {
// Daoのメソッドを取得
Method method = context.getMethod();
// Daoに指定したConfigを取得
Config config = context.getConfig();
...
}
...
}
それから、Entityクラスにて、@GeneratedValueや@Versionを注釈したプロパティをドメインクラスの型で定義できるようにしました。
先日行った、@DeleGateで互換性のあるDaoやEntityを受け入れる修正(https://gist.github.com/4355837) は一旦取り除いてあります。
ありがとうございます。早速試してみました。
一点はまりましたが、上手くいきました!!
ハマったのは以下のポイントです。
@Entity
public abstract class AbstractEntity {
@Column
public DateTime createdAt;
@Column
public DateTime updatedAt;
}
@Entity
public abstract class LogicalDeletable extends AbstractEntity {
@Column
protected DateTime deletedAt;
}
public class InitializeTimestampEntityListener implements EntityListener<AbstractEntity> {
@Override
public void preInsert(final AbstractEntity entity, final PreInsertContext context) {
entity.createdAt = DateTime.now();
entity.updatedAt = DateTime.now();
}
@Override
public void preUpdate(final AbstractEntity entity, final PreUpdateContext context) {
entity.updatedAt = DateTime.now();
}
...
}
public class LogicalDeletableEntityListener extends InitializeTimestampEntityListener {
@Override
public void preUpdate(final AbstractEntity entity, final PreUpdateContext context) {
super.preUpdate(entity, context);
if (context.getMethod().isAnnotationPresent(LogicalDeletion.class)) {
entity.deletedAt = DateTime.now(); // 型があわないので deletedAt にアクセスできない!
}
}
}
Entity の型があわずに deletedAt
にアクセスできない感じです。
以下のようにすると Java Compiler がエラー吐いて落ちます^^;;;
public class InitializeTimestampEntityListener<T extends AbstractEntity> implements EntityListener<T> {
...
}
public class LogicalDeletableEntityListener extends InitializeTimestampEntityListener<LogicalDeletable> {
...
}
一応、以下のようにすることで対応は可能でした。
public abstract class AbstractInitializeTimestampEntityListener<T extends AbstractEntity> implements EntityListener<T> {
@Override
public void preInsert(final T entity, final PreInsertContext context) {
entity.createdAt = DateTime.now();
entity.updatedAt = DateTime.now();
}
@Override
public void preUpdate(final T entity, final PreUpdateContext context) {
entity.updatedAt = DateTime.now();
}
}
public class InitializeTimestampEntityListener extends AbstractInitializeTimestampEntityListener<AbstractEntity> {}
public class LogicalDeletableEntityListener extends AbstractInitializeTimestampEntityListener<LogicalDeletableEntity> {
@Override
public void preUpdate(final LogicalDeletableEntity entity, final PreUpdateContext context) {
super.preUpdate(entity, context);
if (context.getMethod().isAnnotationPresent(LogicalDeletion.class)) {
entity.deletedAt = DateTime.now();
}
}
}
取り急ぎご報告まで
もっと別の方法も思いつきました。Domaの修正は必要ですが、@DeleGateではなく、EntityListenerを使うのはどうでしょうか?前後でフックしたいならこちらのほうがいいかもしれないです。欠点は、すべてのEntityにEntityListenerを指定する必要があることです。