Java 中的元数据映射模式:无缝连接对象和数据存储
大约 5 分钟
元数据映射设计模式的意图
元数据映射设计模式旨在以一种保持数据库模式和对象模型解耦且可管理的方式管理数据库记录和 Java 对象之间的映射。
元数据映射模式的详细解释及实际示例
实际示例
在线零售系统可以看作是元数据映射设计模式的实际示例。在这些系统中,产品通常具有不同的属性,具体取决于其类别。例如,电子产品可能具有电池续航时间和屏幕尺寸等属性,而服装可能具有尺寸和面料类型等属性。使用元数据映射,系统可以动态地将这些不同的属性映射到产品对象,而无需修改底层类结构。这种灵活性允许在引入新类别和属性时轻松更新和管理产品属性,从而确保系统能够随着不断变化的产品环境而发展。
通俗地说
元数据映射指定了类和表之间的映射,以便我们可以将任何数据库的表视为 Java 类。
维基百科说
创建一个“虚拟对象数据库”,可以在编程语言中使用。
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 设计模式
- 数据映射器:元数据映射通常在更广泛的数据映射器模式中使用,以促进映射过程。
- 活动记录:与活动记录不同,元数据映射将数据访问逻辑与领域实体分离。
- 仓储:与仓储模式配合良好,进一步抽象数据访问,允许更复杂的领域逻辑与数据映射 cleanly 分离。