3 import java.sql.Connection;
4 import java.sql.ResultSet;
5 import java.sql.ResultSetMetaData;
6 import java.sql.SQLException;
7 import java.sql.Statement;
8 import java.util.ArrayList;
9 import java.util.HashMap;
10 import java.util.List;
12 import java.util.Objects;
13 import java.util.stream.Collectors;
15 import javax.persistence.EntityManager;
16 import javax.persistence.metamodel.Type;
18 import org.hibernate.Session;
19 import org.hibernate.jdbc.ReturningWork;
21 // DO NOT MODIFY THIS CLASS.
24 * Contains various methods for accessing the database underlying an EntityManager.
26 * Note that the caller is responsible for dealing with possible exceptions as well as doing the connection handling. A
27 * connection will not be closed even if a fatal error occurs. However, other SQL resources i.e.,
28 * {@link Statement Statements} and {@link ResultSet ResultSets} created within the methods, which are not returned to
29 * the caller, are closed before the method returns.
31 public class DatabaseGateway {
33 private final EntityManager em;
35 public DatabaseGateway(EntityManager em) {
40 * Returns a list of all table-names for the given database/connection.
42 * @return List of table names
44 public List<String> getTables() {
45 return getSession().doReturningWork(new CollectionWork<>("show tables", rs -> rs.getString(1)));
49 * Returns a list of all column names in the given table.
51 * @param tableName the table
52 * @return a list of column names
54 public List<String> getColumns(String tableName) {
55 return getColumnsDefinitions(tableName).stream().map(m -> m.get("COLUMN_NAME")).collect(Collectors.toList());
58 public List<Map<String, String>> getColumnsDefinitions(String tableName) {
59 String sql = String.format("SELECT * FROM information_schema.columns "
60 + "WHERE table_name='%s'", tableName.toUpperCase());
62 return getSession().doReturningWork(new QueryWork<List<Map<String, String>>>(sql) {
64 protected List<Map<String, String>> execute(ResultSet rs) throws SQLException {
65 List<Map<String, String>> list = new ArrayList<>();
67 ResultSetMetaData meta = rs.getMetaData();
68 Map<String, String> map = new HashMap<>();
69 for (int i = 1; i <= meta.getColumnCount(); i++) {
70 String key = meta.getColumnName(i);
71 String value = rs.getString(key);
82 * Returns the java types of all managed entity types.
84 * @return a list of java types
86 public List<Class<?>> getManagedJavaTypes() {
87 return em.getMetamodel()
88 .getManagedTypes().stream()
89 .map(Type::getJavaType)
90 .collect(Collectors.toList());
94 * Checks if the named table can be accessed via the given EntityManager.
96 * @param tableName the name of the table to find
97 * @return {@code true} if the database schema contains a table with the given name, {@code false} otherwise
99 public boolean isTable(final String tableName) {
100 return getSession().doReturningWork(new QueryWork<Boolean>("show tables") {
102 public Boolean execute(ResultSet rs) throws SQLException {
104 String tbl = rs.getString(1);
105 if (tbl.equalsIgnoreCase(tableName)) {
116 * Checks whether a certain database table contains a column with the given
119 * @param tableName the name of the table to check
120 * @param column the name of the column to find
121 * @return {@code true} if the table contains the column, {@code false} otherwise
123 public boolean isColumnInTable(String tableName, String column) {
124 String sql = String.format(
125 "SELECT * FROM information_schema.columns WHERE table_name='%s' and column_name='%s'",
126 tableName.toUpperCase(), column.toUpperCase()
129 return getSession().doReturningWork(new HasAtLeastOneEntry(sql));
133 * Checks whether a table contains a column of the given type and length.
135 * @param tableName the table to look for
136 * @param column the expected column name
137 * @param type the expected column type
138 * @param length the expected column length
139 * @return true if the information schema has at least one such column
141 public boolean isColumnInTableWithType(String tableName, String column, String type, String length) {
142 String sql = String.format("SELECT * FROM information_schema.columns "
143 + "WHERE table_name='%s' and column_name='%s' and "
144 + "type_name='%s' and character_maximum_length='%s'",
145 tableName.toUpperCase(), column.toUpperCase(), type.toUpperCase(), length);
147 return getSession().doReturningWork(new HasAtLeastOneEntry(sql));
151 * Checks whether a certain table contains an index for the given column
154 * @param tableName the name of the table to check
155 * @param indexName the name of the column the index is created for
156 * @param nonUnique {@code true} if the index is non unique, {@code false} otherwise
157 * @return {@code true} if the index exists, {@code false} otherwise
159 public boolean isIndex(String tableName, String indexName, boolean nonUnique) {
161 String sql = String.format(
162 "SELECT * FROM information_schema.indexes WHERE table_name='%s' and column_name='%s' and non_unique='%s'",
163 tableName.toUpperCase(), indexName.toUpperCase(), nonUnique ? "1" : "0"
166 return getSession().doReturningWork(new HasAtLeastOneEntry(sql));
169 public boolean isComposedIndex(String tableName, String columnName1, String columnName2) {
170 String indexName1 = getIndexName(tableName, columnName1);
171 String indexName2 = getIndexName(tableName, columnName2);
173 return Objects.nonNull(indexName1) && Objects.equals(indexName1, indexName2);
176 private String getIndexName(String tableName, String columnName) {
177 String sql = String.format(
178 "SELECT index_name FROM information_schema.indexes WHERE table_name='%s' and column_name='%s'",
179 tableName.toUpperCase(), columnName.toUpperCase()
182 return getSession().doReturningWork(new QueryWork<String>(sql) {
184 protected String execute(ResultSet rs) throws SQLException {
185 return (rs.next()) ? rs.getString(1) : null;
191 * Checks whether the given column of a certain table can contain {@code NULL} values.
193 * @param tableName the name of the table to check
194 * @param columnName the name of the column to check
195 * @return {@code true} if the column is nullable, {@code false} otherwise
197 public boolean isNullable(String tableName, String columnName) {
198 String sql = String.format(
199 "SELECT * FROM information_schema.columns " +
200 "WHERE table_name='%s' and column_name='%s' and IS_NULLABLE=true",
201 tableName.toUpperCase(), columnName.toUpperCase()
204 return getSession().doReturningWork(new HasAtLeastOneEntry(sql));
208 * Deletes all data from all tables that can be accessed via the given EntityManager.
210 public void truncateTables() {
211 List<String> tables = getTables();
212 tables.removeIf(t -> t.toLowerCase().startsWith("hibernate"));
214 getSession().doWork(connection -> {
215 try (Statement stmt = connection.createStatement()) {
216 stmt.addBatch("SET FOREIGN_KEY_CHECKS=0");
217 for (String table : tables) {
218 stmt.addBatch("TRUNCATE TABLE " + table);
220 stmt.addBatch("SET FOREIGN_KEY_CHECKS=1");
226 public Session getSession() {
227 return em.unwrap(Session.class);
230 public interface StatementWork<T> extends ReturningWork<T> {
232 default T execute(Connection connection) throws SQLException {
233 try (Statement stmt = connection.createStatement()) {
234 return execute(stmt);
238 T execute(Statement stmt) throws SQLException;
241 public static abstract class QueryWork<T> implements StatementWork<T> {
242 private final String sql;
244 public QueryWork(String sql) {
249 public T execute(Statement stmt) throws SQLException {
250 try (ResultSet rs = stmt.executeQuery(sql)) {
255 protected abstract T execute(ResultSet rs) throws SQLException;
258 public static class HasAtLeastOneEntry extends QueryWork<Boolean> {
260 public HasAtLeastOneEntry(String sql) {
265 protected Boolean execute(ResultSet rs) throws SQLException {
270 public static class CollectionWork<T> extends QueryWork<List<T>> {
272 private final CheckedFunction<ResultSet, T, SQLException> extractor;
274 public CollectionWork(String sql, CheckedFunction<ResultSet, T, SQLException> extractor) {
276 this.extractor = extractor;
280 protected List<T> execute(ResultSet rs) throws SQLException {
281 List<T> list = new ArrayList<>();
283 list.add(extractor.apply(rs));
290 public interface CheckedFunction<T, R, E extends Exception> {
291 R apply(T t) throws E;