14.10.2019
Hibernate — динамическая связь между базами
Hibernate — отличная библиотека для работы с БД в java. Встала недавно задача связи таблиц в разных базах. Гуру конечно скажут, это делается через аннотацию в сущности таблицы, но у этого способа есть свои ограничения:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Entity @Table(name = "Game",schema = "base1",catalog = "base1") public class Gamers { @Id @Getter @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; @Getter @NaturalId @Column(name = "name", nullable = false, unique = true) private String name; } |
и собственно 2 класса
1 2 3 4 5 6 7 8 9 10 11 12 |
@Entity @Table(name = "Player",schema = "base2",catalog = "base2") @Data public class Players { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Getter private int $id; @OneToOne(optional = false, cascade = { CascadeType.ALL }) @JoinColumn(name = "id") private Gamers gamer; } |
Но в моей задачи стояло динамическое изменение имени базы, из конфига. Гугл утверждал, методами библиотеки это сделать нельзя (у меня еще и конфиг кастомный, не пользуюсь xml настройкой, хочу свои конфиги и все тут). Не буду описывать все множественные изыскания на эту тему, просто расскажу как в конце концов решилась проблема, надеюсь это кому-то поможет, поскольку в интернете решения готового так и не нашлось.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
package ru.package; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.util.Map; import java.lang.reflect.Proxy; import org.hibernate.HibernateException; import org.hibernate.cfg.Configuration; import java.util.Properties; import org.hibernate.SessionFactory; import java.util.HashMap; import javax.persistence.Table; public class HibernateSessionFactory { /** * Карта фабрик, если много подключений к БД */ private static final HashMap /** * Создает строки параметров подключчения для конфигурации фабрики * * @param server * @param user * @param pass * @param db * @param port * @return */ private static Properties getProperty(String server, String user, String pass, String db, String port) { Properties prop = new Properties(); prop.setProperty("hibernate.connection.url", "jdbc:mysql://" + server + ":" + port + "/" + db); prop.setProperty("hibernate.connection.username", user); prop.setProperty("hibernate.connection.password", pass); prop.setProperty("dialect", "org.hibernate.dialect.MySQLDialect"); return prop; } /** * Метод сборки фабрики, принимает enum от конфига который содержит * параметры подключения и список классов сущностей * * @param base * @return */ public static SessionFactory buildFactorySession(TypeDB base) { try { Configuration configurator = new Configuration() .addPackage("com.concretepage.persistence") .addProperties(getProperty(base.getServer(), base.getUser(), base.getPassword(), base.getDB(), base.getPort())); base.getModels().forEach(model -> { setDBInClass(model, base.getDB()); configurator.addAnnotatedClass(model); }); sessionFactory.put(base.toString(), configurator.buildSessionFactory()); } catch (HibernateException e) { log.error("Exception:" + e); } return sessionFactory.get(base.toString()); } /** * Возвращает фабрику, если она есть, или создает ее, если нет * принимает enum от конфига который содержит * параметры подключения и список классов сущностей * * @param base */ public static SessionFactory getSessionFactory(TypeDB base) { String nameFactory = base.toString(); if (!sessionFactory.containsKey(nameFactory)) { return buildFactorySession(base); } else { return sessionFactory.get(nameFactory); } } /** * Устанавливает аннотацию перед загрузкой класса в фабрику * * @param cl * @param table */ private static void setDBInClass(final Class cl, final String table) { changeAnnotationValue(cl.getAnnotation(Table.class), "schema", table); changeAnnotationValue(cl.getAnnotation(Table.class), "catalog", table); } /** * Получает класс аннотации и меняет содержимое его полей * * @param annotation * @param key * @param newValue * @return */ private static Object changeAnnotationValue(final Annotation annotation, final String key, final Object newValue) { final Object handler = Proxy.getInvocationHandler(annotation); Field f; try { f = handler.getClass().getDeclaredField("memberValues"); } catch (NoSuchFieldException | SecurityException e) { final Exception ex; throw new IllegalStateException(e); } f.setAccessible(true); Map try { memberValues = (Map } catch (IllegalArgumentException | IllegalAccessException e) { throw new IllegalStateException(e); } final Object oldValue = memberValues.get(key); if (oldValue == null || oldValue.getClass() != newValue.getClass()) { throw new IllegalArgumentException(); } memberValues.put(key, newValue); return oldValue; } } |
Самым простым решением, оказалось рефлексией изменять в сущности аннотацию и уже после этого скармливать это фабрике. Само собой в данном виде, пользователь БД должен иметь доступ в все нужные базы. Как в моем случае, если ему нужна всего пара колонок из таблицы другой базы — можно дать ему права только на чтение, только на них, и не сильно переживать.
1 2 3 4 |
CREATE USER 'tested'@'localhost'; FLUSH PRIVILEGES; GRANT SELECT (id,title) ON fab.goods TO 'tested'@'localhost'; |