generateSampleUsers()
+}
+
+com.iluwatar.metamapping.service.UserService <.. com.iluwatar.metamapping.App
+com.iluwatar.metamapping.model.User <.. com.iluwatar.metamapping.service.UserService
+com.iluwatar.metamapping.utils.HibernateUtil <.. com.iluwatar.metamapping.service.UserService
+com.iluwatar.metamapping.utils.DatabaseUtil <-- com.iluwatar.metamapping.utils.HibernateUtil
+@enduml
\ No newline at end of file
diff --git a/metadata-mapping/pom.xml b/metadata-mapping/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..43c9621dfacacdabe5cff9ac402ab7d9633cdb6e
--- /dev/null
+++ b/metadata-mapping/pom.xml
@@ -0,0 +1,87 @@
+
+
+
+
+ java-design-patterns
+ com.iluwatar
+ 1.26.0-SNAPSHOT
+
+ 4.0.0
+
+ metadata-mapping
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ com.h2database
+ h2
+
+
+ org.hibernate
+ hibernate-core
+
+
+ com.h2database
+ h2
+
+
+ javax.xml.bind
+ jaxb-api
+
+
+ com.sun.xml.bind
+ jaxb-impl
+
+
+ com.sun.istack
+ istack-commons-runtime
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+
+
+
+ com.iluwatar.metamapping.App
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/metadata-mapping/src/main/java/com/iluwatar/metamapping/App.java b/metadata-mapping/src/main/java/com/iluwatar/metamapping/App.java
new file mode 100644
index 0000000000000000000000000000000000000000..ff0377590dcdc60c6510921e8815be4aee3aa9f7
--- /dev/null
+++ b/metadata-mapping/src/main/java/com/iluwatar/metamapping/App.java
@@ -0,0 +1,72 @@
+package com.iluwatar.metamapping;
+
+import com.iluwatar.metamapping.model.User;
+import com.iluwatar.metamapping.service.UserService;
+import com.iluwatar.metamapping.utils.DatabaseUtil;
+import java.util.List;
+import lombok.extern.slf4j.Slf4j;
+import org.hibernate.service.ServiceRegistry;
+
+/**
+ * Metadata Mapping specifies the mapping
+ * between classes and tables so that
+ * we could treat a table of any database like a Java class.
+ *
+ * With hibernate, we achieve list/create/update/delete/get operations:
+ * 1)Create the H2 Database in {@link DatabaseUtil}.
+ * 2)Hibernate resolve hibernate.cfg.xml and generate service like save/list/get/delete.
+ * For learning metadata mapping pattern, we go deeper into Hibernate here:
+ * a)read properties from hibernate.cfg.xml and mapping from *.hbm.xml
+ * b)create session factory to generate session interacting with database
+ * c)generate session with factory pattern
+ * d)create query object or use basic api with session,
+ * hibernate will convert all query to database query according to metadata
+ * 3)We encapsulate hibernate service in {@link UserService} for our use.
+ * @see org.hibernate.cfg.Configuration#configure(String)
+ * @see org.hibernate.cfg.Configuration#buildSessionFactory(ServiceRegistry)
+ * @see org.hibernate.internal.SessionFactoryImpl#openSession()
+ */
+@Slf4j
+public class App {
+ /**
+ * Program entry point.
+ *
+ * @param args command line args.
+ * @throws Exception if any error occurs.
+ */
+ public static void main(String[] args) throws Exception {
+ // 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();
+ }
+
+ /**
+ * Generate users.
+ *
+ * @return list of users.
+ */
+ public static List 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);
+ }
+}
\ No newline at end of file
diff --git a/metadata-mapping/src/main/java/com/iluwatar/metamapping/model/User.java b/metadata-mapping/src/main/java/com/iluwatar/metamapping/model/User.java
new file mode 100644
index 0000000000000000000000000000000000000000..0bf2575b151c003a17375bdcc7d487732c437c0e
--- /dev/null
+++ b/metadata-mapping/src/main/java/com/iluwatar/metamapping/model/User.java
@@ -0,0 +1,29 @@
+package com.iluwatar.metamapping.model;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+/**
+ * User Entity.
+ */
+@Setter
+@Getter
+@ToString
+public class User {
+ private Integer id;
+ private String username;
+ private String password;
+
+ public User() {}
+
+ /**
+ * Get a user.
+ * @param username user name
+ * @param password user password
+ */
+ public User(String username, String password) {
+ this.username = username;
+ this.password = password;
+ }
+}
\ No newline at end of file
diff --git a/metadata-mapping/src/main/java/com/iluwatar/metamapping/service/UserService.java b/metadata-mapping/src/main/java/com/iluwatar/metamapping/service/UserService.java
new file mode 100644
index 0000000000000000000000000000000000000000..1f85be0d51b3925dd0c327c2de49c741834bf1a3
--- /dev/null
+++ b/metadata-mapping/src/main/java/com/iluwatar/metamapping/service/UserService.java
@@ -0,0 +1,114 @@
+package com.iluwatar.metamapping.service;
+
+import com.iluwatar.metamapping.model.User;
+import com.iluwatar.metamapping.utils.HibernateUtil;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.extern.slf4j.Slf4j;
+import org.hibernate.HibernateException;
+import org.hibernate.SessionFactory;
+
+/**
+ * Service layer for user.
+ */
+@Slf4j
+public class UserService {
+ private static final SessionFactory factory = HibernateUtil.getSessionFactory();
+
+ /**
+ * List all users.
+ * @return list of users
+ */
+ public List listUser() {
+ LOGGER.info("list all users.");
+ List users = new ArrayList<>();
+ try (var session = factory.openSession()) {
+ var tx = session.beginTransaction();
+ List 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;
+ }
+
+ /**
+ * Add a user.
+ * @param user user entity
+ * @return user id
+ */
+ public int createUser(User user) {
+ LOGGER.info("create user: " + user.getUsername());
+ var id = -1;
+ try (var session = factory.openSession()) {
+ var tx = session.beginTransaction();
+ id = (Integer) session.save(user);
+ tx.commit();
+ } catch (HibernateException e) {
+ LOGGER.debug("fail to create user", e);
+ }
+ LOGGER.info("create user " + user.getUsername() + " at " + id);
+ return id;
+ }
+
+ /**
+ * Update user.
+ * @param id user id
+ * @param user new user entity
+ */
+ public void updateUser(Integer id, User user) {
+ LOGGER.info("update user at " + id);
+ try (var session = factory.openSession()) {
+ var tx = session.beginTransaction();
+ user.setId(id);
+ session.update(user);
+ tx.commit();
+ } catch (HibernateException e) {
+ LOGGER.debug("fail to update user", e);
+ }
+ }
+
+ /**
+ * Delete user.
+ * @param id user id
+ */
+ public void deleteUser(Integer id) {
+ LOGGER.info("delete user at: " + id);
+ try (var session = factory.openSession()) {
+ var tx = session.beginTransaction();
+ var user = session.get(User.class, id);
+ session.delete(user);
+ tx.commit();
+ } catch (HibernateException e) {
+ LOGGER.debug("fail to delete user", e);
+ }
+ }
+
+ /**
+ * Get user.
+ * @param id user id
+ * @return deleted user
+ */
+ public User getUser(Integer id) {
+ LOGGER.info("get user at: " + id);
+ User user = null;
+ try (var session = factory.openSession()) {
+ var tx = session.beginTransaction();
+ user = session.get(User.class, id);
+ tx.commit();
+ } catch (HibernateException e) {
+ LOGGER.debug("fail to get user", e);
+ }
+ return user;
+ }
+
+ /**
+ * Close hibernate.
+ */
+ public void close() {
+ HibernateUtil.shutdown();
+ }
+}
\ No newline at end of file
diff --git a/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/DatabaseUtil.java b/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/DatabaseUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..b6d0200e825954262a80c5cb3ef2a6847fd9289f
--- /dev/null
+++ b/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/DatabaseUtil.java
@@ -0,0 +1,39 @@
+package com.iluwatar.metamapping.utils;
+
+import java.sql.SQLException;
+import lombok.extern.slf4j.Slf4j;
+import org.h2.jdbcx.JdbcDataSource;
+
+/**
+ * Create h2 database.
+ */
+@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`;"
+ + "CREATE TABLE `user` (\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"
+ + ");";
+
+ /**
+ * Hide constructor.
+ */
+ private DatabaseUtil() {}
+
+ /**
+ * Create database.
+ */
+ 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/HibernateUtil.java b/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/HibernateUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..ba405eb744504a21feda6b675854e2c4f133cfcb
--- /dev/null
+++ b/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/HibernateUtil.java
@@ -0,0 +1,45 @@
+package com.iluwatar.metamapping.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.hibernate.SessionFactory;
+import org.hibernate.cfg.Configuration;
+
+/**
+ * Manage hibernate.
+ */
+@Slf4j
+public class HibernateUtil {
+
+ private static final SessionFactory sessionFactory = buildSessionFactory();
+
+ /**
+ * Hide constructor.
+ */
+ private HibernateUtil() {}
+
+ /**
+ * Build session factory.
+ * @return session factory
+ */
+ private static SessionFactory buildSessionFactory() {
+ // Create the SessionFactory from hibernate.cfg.xml
+ return new Configuration().configure().buildSessionFactory();
+ }
+
+ /**
+ * Get session factory.
+ * @return session factory
+ */
+ public static SessionFactory getSessionFactory() {
+ return sessionFactory;
+ }
+
+ /**
+ * Close session factory.
+ */
+ public static void shutdown() {
+ // Close caches and connection pools
+ getSessionFactory().close();
+ }
+
+}
\ No newline at end of file
diff --git a/metadata-mapping/src/main/resources/com/iluwatar/metamapping/model/User.hbm.xml b/metadata-mapping/src/main/resources/com/iluwatar/metamapping/model/User.hbm.xml
new file mode 100644
index 0000000000000000000000000000000000000000..cd63c552d65d4b6bbd4670a2abe4723d7735b16e
--- /dev/null
+++ b/metadata-mapping/src/main/resources/com/iluwatar/metamapping/model/User.hbm.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/metadata-mapping/src/main/resources/hibernate.cfg.xml b/metadata-mapping/src/main/resources/hibernate.cfg.xml
new file mode 100644
index 0000000000000000000000000000000000000000..18dc198e84ba3d89799b0e9f2fae770a61ffd0d7
--- /dev/null
+++ b/metadata-mapping/src/main/resources/hibernate.cfg.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+ jdbc:h2:mem:metamapping
+ org.h2.Driver
+
+ 1
+
+ org.hibernate.dialect.H2Dialect
+
+ false
+
+ create-drop
+
+
+
\ No newline at end of file
diff --git a/metadata-mapping/src/test/java/com/iluwatar/metamapping/AppTest.java b/metadata-mapping/src/test/java/com/iluwatar/metamapping/AppTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..127ddad0f94fedccb9853c91e59f964ecce53385
--- /dev/null
+++ b/metadata-mapping/src/test/java/com/iluwatar/metamapping/AppTest.java
@@ -0,0 +1,20 @@
+package com.iluwatar.metamapping;
+
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+/**
+ * Tests that metadata mapping example runs without errors.
+ */
+class AppTest {
+ /**
+ * Issue: Add at least one assertion to this test case.
+ *
+ * Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])}
+ * throws an exception.
+ */
+ @Test
+ void shouldExecuteMetaMappingWithoutException() {
+ assertDoesNotThrow(() -> App.main(new String[]{}));
+ }
+}
diff --git a/pom.xml b/pom.xml
index 5c3634b95aa217db13f9ecadd251065c2b975676..e1573a7377676d292d36c57f929730776d73e052 100644
--- a/pom.xml
+++ b/pom.xml
@@ -75,6 +75,7 @@
3.0
1.4.8
2.7
+ 4.0.1
https://sonarcloud.io
iluwatar
@@ -227,6 +228,7 @@
lockable-object
fanout-fanin
domain-model
+ metadata-mapping
@@ -377,6 +379,11 @@
commons-io
${commons-io.version}
+
+ com.sun.istack
+ istack-commons-runtime
+ ${istack-commons-runtime.version}
+