Javaにおけるメタデータマッピングパターン:オブジェクトとデータストアをシームレスに橋渡し
メタデータマッピングデザインパターンの意図
メタデータマッピングデザインパターンは、データベースのレコードとJavaオブジェクト間のマッピングを、データベーススキーマとオブジェクトモデルを疎結合かつ管理しやすい状態に保つように設計されています。
実世界の例を用いたメタデータマッピングパターンの詳細な説明
実世界の例
メタデータマッピングデザインパターンの類似した実世界の例は、オンライン小売システムに見られます。このようなシステムでは、製品はカテゴリによって異なる属性を持つことがよくあります。たとえば、電化製品にはバッテリー寿命や画面サイズなどの属性があり、衣料品にはサイズや生地の種類などの属性がある場合があります。メタデータマッピングを使用すると、システムは基礎となるクラス構造を変更せずに、これらの異なる属性を製品オブジェクトに動的にマッピングできます。この柔軟性により、新しいカテゴリや属性が導入されたときに製品属性を簡単に更新および管理でき、システムが変化する製品の状況に合わせて進化することを保証できます。
平易な言葉で
メタデータマッピングは、クラスとテーブル間のマッピングを指定し、任意のデータベースのテーブルをJavaクラスのように扱うことができるようにします。
Wikipediaによると
プログラミング言語内から使用できる「仮想オブジェクトデータベース」を作成します。
Javaにおけるメタデータマッピングパターンのプログラム例
Hibernate ORMツールは、XMLまたはコード内のアノテーションを使用して、クラスとテーブル間のマッピングを指定するためにメタデータマッピングパターンを使用します。
h2
データベースのuser_account
テーブルの情報を参照する例を示します。まず、h2
でuser_account
テーブルを作成します。
@Slf4j
public class DatabaseUtil {
private static final String DB_URL = "jdbc:h2:mem:metamapping";
private static final String CREATE_SCHEMA_SQL = "DROP TABLE IF EXISTS `user_account`;"
+ "CREATE TABLE `user_account` (\n"
+ " `id` int(11) NOT NULL AUTO_INCREMENT,\n"
+ " `username` varchar(255) NOT NULL,\n"
+ " `password` varchar(255) NOT NULL,\n"
+ " PRIMARY KEY (`id`)\n"
+ ");";
static {
LOGGER.info("create h2 database");
var source = new JdbcDataSource();
source.setURL(DB_URL);
try (var statement = source.getConnection().createStatement()) {
statement.execute(CREATE_SCHEMA_SQL);
} catch (SQLException e) {
LOGGER.error("unable to create h2 data source", e);
}
}
}
対応して、ここに基本的なUser
エンティティがあります。
@Setter
@Getter
@ToString
public class User {
private Integer id;
private String username;
private String password;
public User(String username, String password) {
this.username = username;
this.password = password;
}
}
次に、テーブルとオブジェクト間のマッピングを示すxml
ファイルを作成します。
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.iluwatar.metamapping.model.User" table="user_account">
<id name="id" type="java.lang.Integer" column="id">
<generator class="native"/>
</id>
<property name="username" column="username" type="java.lang.String"/>
<property name="password" column="password" type="java.lang.String"/>
</class>
</hibernate-mapping>
Hibernate
を使用してマッピングを解決し、データベースに接続します。その設定は次のとおりです。
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- JDBC Database connection settings -->
<property name="connection.url">jdbc:h2:mem:metamapping</property>
<property name="connection.driver_class">org.h2.Driver</property>
<!-- JDBC connection pool settings ... using built-in test pool -->
<property name="connection.pool_size">1</property>
<!-- Select our SQL dialect -->
<property name="dialect">org.hibernate.dialect.H2Dialect</property>
<!-- Echo the SQL to stdout -->
<property name="show_sql">false</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">create-drop</property>
<mapping resource="com/iluwatar/metamapping/model/User.hbm.xml" />
</session-factory>
</hibernate-configuration>
次に、Hibernate
を使用してオブジェクトのようにテーブルにアクセスできます。以下にいくつかのCRUDを示します。
@Slf4j
public class UserService {
private static final SessionFactory factory = HibernateUtil.getSessionFactory();
public List<User> listUser() {
LOGGER.info("list all users.");
List<User> users = new ArrayList<>();
try (var session = factory.openSession()) {
var tx = session.beginTransaction();
List<User> userIter = session.createQuery("FROM User").list();
for (var iterator = userIter.iterator(); iterator.hasNext();) {
users.add(iterator.next());
}
tx.commit();
} catch (HibernateException e) {
LOGGER.debug("fail to get users", e);
}
return users;
}
// other CRUDs ->
// ...
public void close() {
HibernateUtil.shutdown();
}
}
これは、例を実行するためのmain
関数を持つApp
クラスです。
@Slf4j
public class App {
public static void main(String[] args) {
// get service
var userService = new UserService();
// use create service to add users
for (var user : generateSampleUsers()) {
var id = userService.createUser(user);
LOGGER.info("Add user" + user + "at" + id + ".");
}
// use list service to get users
var users = userService.listUser();
LOGGER.info(String.valueOf(users));
// use get service to get a user
var user = userService.getUser(1);
LOGGER.info(String.valueOf(user));
// change password of user 1
user.setPassword("new123");
// use update service to update user 1
userService.updateUser(1, user);
// use delete service to delete user 2
userService.deleteUser(2);
// close service
userService.close();
}
public static List<User> generateSampleUsers() {
final var user1 = new User("ZhangSan", "zhs123");
final var user2 = new User("LiSi", "ls123");
final var user3 = new User("WangWu", "ww123");
return List.of(user1, user2, user3);
}
}
コンソール出力
14:44:17.792 [main] INFO org.hibernate.Version - HHH000412: Hibernate ORM core version 5.6.12.Final
14:44:17.977 [main] INFO o.h.annotations.common.Version - HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
14:44:18.216 [main] WARN o.hibernate.orm.connections.pooling - HHH10001002: Using Hibernate built-in connection pool (not for production use!)
14:44:18.217 [main] INFO o.hibernate.orm.connections.pooling - HHH10001005: using driver [org.h2.Driver] at URL [jdbc:h2:mem:metamapping]
14:44:18.217 [main] INFO o.hibernate.orm.connections.pooling - HHH10001001: Connection properties: {}
14:44:18.217 [main] INFO o.hibernate.orm.connections.pooling - HHH10001003: Autocommit mode: false
14:44:18.219 [main] INFO o.h.e.j.c.i.DriverManagerConnectionProviderImpl - HHH000115: Hibernate connection pool size: 1 (min=1)
14:44:18.276 [main] INFO org.hibernate.dialect.Dialect - HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
14:44:18.463 [main] INFO o.hibernate.orm.connections.access - HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@73a8e994] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
14:44:18.465 [main] INFO o.hibernate.orm.connections.access - HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@7affc159] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
14:44:18.470 [main] INFO o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
14:44:18.473 [main] INFO c.i.metamapping.service.UserService - create user: ZhangSan
14:44:18.508 [main] INFO c.i.metamapping.service.UserService - create user ZhangSan at 1
14:44:18.508 [main] INFO com.iluwatar.metamapping.App - Add userUser(id=1, username=ZhangSan, password=zhs123)at1.
14:44:18.508 [main] INFO c.i.metamapping.service.UserService - create user: LiSi
14:44:18.509 [main] INFO c.i.metamapping.service.UserService - create user LiSi at 2
14:44:18.509 [main] INFO com.iluwatar.metamapping.App - Add userUser(id=2, username=LiSi, password=ls123)at2.
14:44:18.509 [main] INFO c.i.metamapping.service.UserService - create user: WangWu
14:44:18.512 [main] INFO c.i.metamapping.service.UserService - create user WangWu at 3
14:44:18.512 [main] INFO com.iluwatar.metamapping.App - Add userUser(id=3, username=WangWu, password=ww123)at3.
14:44:18.512 [main] INFO c.i.metamapping.service.UserService - list all users.
14:44:18.542 [main] INFO com.iluwatar.metamapping.App - [User(id=1, username=ZhangSan, password=zhs123), User(id=2, username=LiSi, password=ls123), User(id=3, username=WangWu, password=ww123)]
14:44:18.542 [main] INFO c.i.metamapping.service.UserService - get user at: 1
14:44:18.545 [main] INFO com.iluwatar.metamapping.App - User(id=1, username=ZhangSan, password=zhs123)
14:44:18.545 [main] INFO c.i.metamapping.service.UserService - update user at 1
14:44:18.548 [main] INFO c.i.metamapping.service.UserService - delete user at: 2
14:44:18.550 [main] INFO o.h.t.s.i.SchemaDropperImpl$DelayedDropActionImpl - HHH000477: Starting delayed evictData of schema as part of SessionFactory shut-down'
14:44:18.550 [main] INFO o.hibernate.orm.connections.access - HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@7b5cc918] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
14:44:18.551 [main] INFO o.hibernate.orm.connections.pooling - HHH10001008: Cleaning up connection pool [jdbc:h2:mem:metamapping]
Javaでメタデータマッピングパターンを使用する場合
Javaアプリケーションで、オブジェクト指向のドメインモデルとリレーショナルデータベース間のギャップを埋める必要がある場合に、ドメインロジックにデータベースクエリをハードコーディングすることなく、メタデータマッピングデザインパターンを使用します。
Javaにおけるメタデータマッピングパターンの実世界での応用
- Hibernate、JPA、EclipseLink、MyBatisなどのオブジェクトリレーショナルマッピング(ORM)フレームワークは、Javaオブジェクトをデータベーステーブルにマッピングするために、メタデータマッピングデザインパターンを頻繁に使用します。
- エンタープライズアプリケーションでデータベース行をドメインオブジェクトにマッピングします。
メタデータマッピングパターンの利点とトレードオフ
利点
- オブジェクトモデルとデータベーススキーマを疎結合にし、独立した進化を可能にします。
- データアクセスに関連するボイラープレートコードを削減します。
- マッピングロジックを集中化し、変更をより管理しやすくします。
トレードオフ
- 抽象化の追加レイヤーにより複雑さを増します。
- 適切に最適化されていない場合、パフォーマンスに影響を与える可能性があります。
関連するJavaデザインパターン
- データマッパー:メタデータマッピングは、マッピングプロセスを容易にするために、より広範なデータマッパーパターン内でよく使用されます。
- アクティブレコード:アクティブレコードとは異なり、メタデータマッピングは、データアクセスロジックをドメインエンティティから分離します。
- リポジトリ:データアクセスをさらに抽象化することにより、リポジトリパターンとうまく連携し、より複雑なドメインロジックをデータマッピングからきれいに分離できるようにします。