Прежде всего, предупреждение: это хак и кошмар дженериков! Слишком много хлопот, на мой взгляд, для удовлетворения вашего требования иметь только пакеты-частные методы в ваших репозиториях.
Во-первых, определить абстрактный объект для работы с:
package reachable.from.everywhere;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
@MappedSuperclass
public abstract class AbstractEntity<K> {
@Id
private K id;
// TODO other attributes common to all entities & JPA annotations
public K getId() {
return this.id;
}
// TODO hashCode() and equals() based on id
}
Это просто абстрактная сущность с общим ключом.
Затем определите абстрактный репозиторий, который работает с абстрактными объектами, который будет расширен всеми другими репозиториями. Это знаменует некоторые магии дженериков, поэтому обратите внимание:
package reachable.from.everywhere;
import java.lang.reflect.ParameterizedType;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
public abstract class AbstractRepo<
K, // key
E extends AbstractEntity<K>, // entity
T extends AbstractRepo.SpringAbstractRepo<K, E, U>, // Spring repo
U extends AbstractRepo<K, E, T, U>> { // self type
@Autowired
private ApplicationContext context;
private T delegate;
@SuppressWarnings("unchecked")
@PostConstruct
private void init() {
ParameterizedType type =
(ParameterizedType) this.getClass().getGenericSuperclass();
// Spring repo is inferred from 3rd param type
Class<T> delegateClass = (Class<T>) type.getActualTypeArguments()[2];
// get an instance of the matching Spring repo
this.delegate = this.context.getBean(delegateClass);
}
protected T repo() {
return this.delegate;
}
protected static abstract class SpringAbstractRepo<K, E, U> {
protected final Class<E> entityClass;
// force subclasses to invoke this constructor
// receives an instance of the enclosing class
// this is just for type inference and also
// because Spring needs subclasses to have
// a constructor that receives the enclosing class
@SuppressWarnings("unchecked")
protected SpringAbstractRepo(U outerRepo) {
ParameterizedType type =
(ParameterizedType) this.getClass().getGenericSuperclass();
// Spring repo is inferred from 3rd param type
this.entityClass = (Class<E>) type.getActualTypeArguments()[1];
}
public E load(K id) {
// this method will be forced to be transactional!
E entity = ...;
// TODO load entity with key = id from database
return entity;
}
// TODO other basic operations
}
}
Пожалуйста, ознакомьтесь с комментариями. Код уродливый, так как в нем много дженериков.Это AbstractRepo
параметризируется 4 общих типов:
- K -> тип ключа сущности это репо будет в заряжена из
- E -> тип лица это репо будет в заряжена из
- T -> тип репо, который будет подвергаться воздействию пружины через внутренний класс, так что механизм весового проксирования может иметь место, сохраняя при этом ваши частные методы в закрытом классе
- U - тип подкласса, который будет распространяться на это
AbstractRepo
Эти общие параметры типа необходимы для того, чтобы ваши конкретные операции репозиториев работали и были безопасными по типу, то есть они не будут компилироваться, если вы попытаетесь использовать неправильный тип.
После этого в методе private
@PostConstruct
, мы получаем класс третьих типа пары общих T
, который является типом репо, которые будут подвергаться воздействию пружинных через внутренний класс. Нам нужно это Class<T>
, чтобы мы могли попросить Spring дать нам фасоль этого класса. Затем мы назначаем этот компонент атрибуту delegate
, который равен private
, и будет доступен через метод protected
repo()
.
В конце есть внутренний класс, чьи потомки будут проксироваться весной. Он определяет некоторые общие ограничения типа и некоторые основные операции. У этого есть специальный конструктор, который делает магию дженериков, чтобы получить класс сущности. Вам понадобится класс объекта позже, либо передать его на ваш ORM (возможно, Hibernate Session
), либо создать экземпляр вашего объекта путем отражения и заполнить его данными, полученными из базы данных (возможно, базовый подход JDBC или Spring JDBC).
Что касается основных операций, я только набросал load()
, который получает идентификатор объекта для загрузки, являющийся этим идентификатором типа K
, и возвращает объект, надежно напечатанный.
Пока все хорошо. Вам нужно будет поместить эти 2 класса в пакет и модуль, доступный из всех других пакетов и модулей вашего приложения, поскольку они будут использоваться в качестве базовых классов для ваших конкретных объектов и репозиториев.
Теперь, в одном конкретном пакете вашего приложения, определить образец лица:
package sample;
import reachable.from.everywhere.AbstractEntity;
public class SampleEntity
extends AbstractEntity<Long> {
private String data;
public String getData() {
return this.data;
}
public void setData(String data) {
this.data = data;
}
}
Это просто образец сущность с data
поле, чей идентификатор типа Long
.
Наконец, определить конкретный SampleRepo
, который управляет экземплярами SampleEntity
:
package sample;
import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import reachable.from.everywhere.AbstractRepo;
// annotation needed to detect inner class bean
@Component
public class SampleRepo
extends AbstractRepo<
Long, // key
SampleEntity, // entity with id of type Long
SampleRepo.SampleSpringRepo, // Spring concrete repo
SampleRepo> { // self type
// here's your package-private method
String method(String s) {
return this.repo().method(s);
}
// here's another package-private method
String anotherMethod(String s) {
return this.repo().anotherMethod(s);
}
// can't be public
// otherwise would be visible from other packages
@Repository
@Transactional
class SampleSpringRepo
extends AbstractRepo.SpringAbstractRepo<
Long, // same as enclosing class 1st param
SampleEntity, // same as enclosing class 2nd param
SampleRepo> { // same as enclosing class 4th param
// constructor and annotation needed for proxying
@Autowired
public SampleSpringRepo(SampleRepo myRepo) {
super(myRepo);
}
public String method(String arg) {
// transactional method
return "method - within transaction - " + arg;
}
public String anotherMethod(String arg) {
// transactional method
return "anotherMethod - within transaction - " + arg;
}
}
}
Опять же, внимательно прочтите комментарии в коде. Этот SampleRepo
доступен для сканирования компонентов пружины через аннотацию @Component
. Это public
, хотя все методы являются частными и частными в соответствии с вашими требованиями.
Эти индивидуально-частные методы не реализованы в этом конкретном классе SampleRepo
.Вместо этого они делегируются через унаследованный метод protected
repo()
во внутренний класс, который должен быть проверен весной.
Этот внутренний класс не является public
. Его область действия является закрытой для пакета, поэтому она не видна для классов вне пакета. Однако его методы - public
, поэтому Spring может перехватывать их с помощью прокси. Этот внутренний класс аннотируется с @Repository
и @Transactional
, в соответствии с вашими потребностями. Он расширяет AbstractRepo.SpringAbstractRepo
внутреннего класса по двум причинам:
- Все основные операции автоматически унаследованные (например,
load()
).
- Для проксирования Spring этот класс должен иметь конструктор, который получает компонент входящего класса, и этот аргумент должен быть
@Autowired
. В противном случае Spring не загрузит приложение. Как AbstractRepo.SpringAbstractRepo
abstract
внутренний класс имеет только один конструктор, и этот конструктор принимает аргумент, который должен быть потомком его AbstractRepo
abstract
ограждающей класса, каждый потомок AbstractRepo.SpringAbstractRepo
внутреннего класса нужно будет использовать super()
в своем конструкторе, передавая экземпляр соответствующий охватывающий класс. Это обеспечивается дженериками, поэтому, если вы попытаетесь передать аргумент неправильного типа, вы получите ошибку компиляции.
В качестве заключительного комментария классы abstract
не являются обязательными. Вы могли бы полностью избежать их, а также все эти дженерики, хотя у вас будет дублирующий код.
Я не пробовал это сам, потому что предпочитаю использовать интерфейсы, но вы можете попробовать изучить атрибут 'proxy-target-class'' ' –
Используя аннотации, что означает @Serge, переводит на '@EnableTransactionManagement (mode = AdviceMode.PROXY, proxyTargetClass = true)'. Я также не уверен, будет ли это достаточно, чтобы прокси-серверы cglib корректно работали в вашем случае. –
К сожалению, этого недостаточно, симптомы остаются неизменными. –