org.babyfish.jimmer.sql.runtime.EntityManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jimmer-sql Show documentation
Show all versions of jimmer-sql Show documentation
A revolutionary ORM framework for both java and kotlin
package org.babyfish.jimmer.sql.runtime;
import org.babyfish.jimmer.lang.Lazy;
import org.babyfish.jimmer.meta.ImmutableProp;
import org.babyfish.jimmer.meta.ImmutableType;
import org.babyfish.jimmer.meta.ModelException;
import org.babyfish.jimmer.meta.TargetLevel;
import org.babyfish.jimmer.meta.impl.AbstractImmutableTypeImpl;
import org.babyfish.jimmer.sql.*;
import org.babyfish.jimmer.sql.association.meta.AssociationType;
import org.babyfish.jimmer.sql.meta.ColumnDefinition;
import org.babyfish.jimmer.sql.meta.MetadataStrategy;
import org.babyfish.jimmer.sql.meta.MiddleTable;
import org.babyfish.jimmer.sql.meta.Storage;
import org.babyfish.jimmer.sql.meta.impl.DatabaseIdentifiers;
import org.babyfish.jimmer.sql.meta.impl.MetaCache;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class EntityManager {
private static final Logger LOGGER = LoggerFactory.getLogger(EntityManager.class);
private final ReadWriteLock reloadingLock = new ReentrantReadWriteLock();
private volatile Data data;
public EntityManager(Class> ... classes) {
this(Arrays.asList(classes));
}
public EntityManager(Collection> classes) {
if (!(classes instanceof Set>)) {
classes = new LinkedHashSet<>(classes);
}
Set qualifiedNames = new HashSet<>();
for (Class> clazz : classes) {
if (!qualifiedNames.add(clazz.getName())) {
throw new IllegalArgumentException(
"Multiple classes with the same qualified name \"" +
clazz.getName() +
"\" but belonging to different class loaders " +
"cannot be registered into the entity manager"
);
}
}
Map map = new LinkedHashMap<>();
for (Class> clazz : classes) {
if (clazz != null) {
ImmutableType immutableType = ImmutableType.get(clazz);
if (!immutableType.isEntity()) {
throw new IllegalArgumentException(
"\"" +
immutableType +
"\" is not entity"
);
}
for (ImmutableType type : immutableType.getAllTypes()) {
map.put(type, new ImmutableTypeInfo());
}
}
}
for (Map.Entry e : map.entrySet()) {
ImmutableType type = e.getKey();
ImmutableTypeInfo info = e.getValue();
if (type.isMappedSuperclass()) {
for (ImmutableType otherType : map.keySet()) {
if (isImplementationType(type, otherType)) {
info.implementationTypes.add(otherType);
}
}
} else {
for (ImmutableType otherType : map.keySet()) {
if (type != otherType && type.isAssignableFrom(otherType)) {
info.allDerivedTypes.add(otherType);
if (otherType.getSuperTypes().contains(type)) {
info.directDerivedTypes.add(otherType);
}
}
}
for (ImmutableProp prop : type.getProps().values()) {
ImmutableType targetType = prop.getTargetType();
if (targetType != null && targetType.isEntity() && !prop.isRemote()) {
ImmutableTypeInfo targetInfo = map.get(targetType);
if (targetInfo == null) {
throw new IllegalArgumentException(
"The target type \"" +
targetType +
"\" of the non-remote property \"" +
prop +
"\" is not manged by the current entity manager"
);
}
targetInfo.backProps.add(prop);
}
}
}
}
for (Map.Entry e : map.entrySet()) {
if (!e.getKey().isEntity()) {
continue;
}
List props = new ArrayList<>();
List backProps = new ArrayList<>();
boolean hasInverseLocalAssociation = false;
for (ImmutableProp prop : e.getKey().getProps().values()) {
if (prop.isRemote()) {
continue;
}
if (prop.getMappedBy() != null) {
hasInverseLocalAssociation = true;
continue;
}
if (prop.isMiddleTableDefinition()) {
props.add(prop);
}
}
if (hasInverseLocalAssociation) {
for (ImmutableProp backProp : e.getValue().backProps) {
if (backProp.getMappedBy() != null || backProp.isRemote()) {
continue;
}
if (backProp.isMiddleTableDefinition()) {
backProps.add(backProp);
} else if (backProp.isReference(TargetLevel.PERSISTENT) && backProp.isColumnDefinition()) {
backProps.add(backProp);
}
}
}
if (!props.isEmpty() || !backProps.isEmpty()) {
e.getValue().dissociationInfo = new DissociationInfo(
Collections.unmodifiableList(props),
Collections.unmodifiableList(backProps)
);
}
}
for (ImmutableTypeInfo info : map.values()) {
info.implementationTypes = Collections.unmodifiableList(info.implementationTypes);
info.directDerivedTypes = Collections.unmodifiableList(info.directDerivedTypes);
info.allDerivedTypes = Collections.unmodifiableList(info.allDerivedTypes);
info.backProps = Collections.unmodifiableList(
info
.backProps
.stream()
// sort is important, that means the order of cascade sql operations is fixed
.sorted(Comparator.comparing(ImmutableProp::toString))
.collect(Collectors.toList())
);
}
map = Collections.unmodifiableMap(map);
Map springDevToolMap = new HashMap<>((map.size() * 4 + 2) / 3);
for (ImmutableType type : map.keySet()) {
springDevToolMap.put(type.getJavaClass().getName(), type);
}
this.data = new Data(map, springDevToolMap);
}
public static EntityManager combine(EntityManager ... entityManagers) {
if (entityManagers.length == 0) {
throw new IllegalArgumentException("No entity managers");
}
if (entityManagers.length == 1) {
return entityManagers[0];
}
Set> classes = new LinkedHashSet<>();
for (EntityManager entityManager : entityManagers) {
for (ImmutableType type : entityManager.getAllTypes(null)) {
if (type.isEntity()) {
classes.add(type.getJavaClass());
}
}
}
return new EntityManager(classes);
}
public static EntityManager fromResources(
@Nullable ClassLoader classLoader,
@Nullable Predicate> predicate
) {
if (classLoader == null) {
classLoader = Thread.currentThread().getContextClassLoader();
}
Set> classes = new LinkedHashSet<>();
try {
Enumeration urls = classLoader.getResources("META-INF/jimmer/entities");
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()))) {
while (true) {
String className = reader.readLine();
if (className == null) {
break;
}
className = className.trim();
if (!className.isEmpty()) {
Class> clazz;
try {
clazz = Class.forName(className, true, classLoader);
} catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Cannot parse class name \"" +
className +
"\" in \"META-INF/jimmer/entities\"",
ex
);
}
if (predicate == null || predicate.test(clazz)) {
classes.add(clazz);
}
}
}
} catch (IOException ex) {
throw new IllegalStateException("Failed to load resource \"" + url + "\"", ex);
}
}
} catch (IOException ex) {
throw new IllegalStateException("Failed to load resources \"META-INF/jimmer/entities\"", ex);
}
return new EntityManager(classes);
}
public Set getAllTypes(String microServiceName) {
if (microServiceName == null) {
return data.map.keySet();
}
Set set = microServiceName.isEmpty() ?
new LinkedHashSet<>((data.map.size() * 4 + 2) / 3) :
new LinkedHashSet<>();
for (ImmutableType type : data.map.keySet()) {
if (type.getMicroServiceName().equals(microServiceName)) {
set.add(type);
}
}
return set;
}
public List getImplementationTypes(ImmutableType type) {
return info(type).implementationTypes;
}
public List getDirectDerivedTypes(ImmutableType type) {
return info(type).directDerivedTypes;
}
public List getAllDerivedTypes(ImmutableType type) {
return info(type).allDerivedTypes;
}
public List getAllBackProps(ImmutableType type) {
return info(type).backProps;
}
@Nullable
public DissociationInfo getDissociationInfo(ImmutableType type) {
return info(type).dissociationInfo;
}
private ImmutableTypeInfo info(ImmutableType type) {
ImmutableTypeInfo info = data.map.get(type);
if (info == null) {
ImmutableType oldType = data.typeMapForSpringDevTools.get(type.getJavaClass().getName());
if (oldType != null) {
LOGGER.info(
"You seem to be using spring-dev-tools (or other multi-ClassLoader technology), " +
"so that some entity metadata changes but the ORM's entire metadata graph " +
"is not updated, now try to reload the EntityManager."
);
try {
reload(type);
} catch (RuntimeException | Error ex) {
throw new IllegalStateException(
"You seem to be using spring-dev-tools (or other multi-ClassLoader technology), " +
"so that some entity metadata changes but the ORM's entire metadata graph " +
"is not updated, jimmer try to reload the EntityManager but meet some problem.",
ex
);
}
info = data.map.get(type);
}
if (info == null) {
throw new IllegalArgumentException(
"\"" + type + "\" is not managed by current EntityManager"
);
}
}
return info;
}
private void reload(ImmutableType immutableType) {
Lock lock;
(lock = reloadingLock.readLock()).lock();
try {
if (data.map.containsKey(immutableType)) {
return;
}
} finally {
lock.unlock();
}
(lock = reloadingLock.writeLock()).lock();
try {
if (data.map.containsKey(immutableType)) {
return;
}
EntityManager newEntityManager = EntityManager.fromResources(
immutableType.getJavaClass().getClassLoader(),
null
);
data = newEntityManager.data;
} finally {
lock.unlock();
}
}
private boolean isImplementationType(ImmutableType mappedSuperClass, ImmutableType type) {
if (!mappedSuperClass.isMappedSuperclass()) {
throw new AssertionError("Internal bug");
}
if (!type.isEntity()) {
return false;
}
if (!mappedSuperClass.isAssignableFrom(type)) {
return false;
}
for (ImmutableType superType : type.getSuperTypes()) {
if (superType.isMappedSuperclass()) {
return true;
}
}
return false;
}
private static class ImmutableTypeInfo {
// For mapped super class
List implementationTypes = new ArrayList<>();
// For entity
List directDerivedTypes = new ArrayList<>();
// For entity
List allDerivedTypes = new ArrayList<>();
// For entity
List backProps = new ArrayList<>();
// For entity
DissociationInfo dissociationInfo;
}
public boolean isActiveMiddleTableProp(ImmutableProp prop) {
return data.activeMiddleTableProps.contains(prop);
}
public Map, ImmutableType> getTypeMapByServiceAndTable(
String microServiceName,
String tableName,
MetadataStrategy strategy
) {
Objects.requireNonNull(microServiceName, "`microServiceName` cannot be null");
tableName = DatabaseIdentifiers.comparableIdentifier(
Objects.requireNonNull(tableName, "`tableName` cannot be null")
);
while (true) {
Map, ImmutableType> type = data.getTypeMap(strategy).get(new Key(microServiceName, tableName));
if (type != null) {
return type;
}
int index = tableName.indexOf('.');
if (index == -1) {
break;
}
tableName = tableName.substring(index + 1);
if (tableName.isEmpty()) {
break;
}
}
return Collections.emptyMap();
}
public void validate(MetadataStrategy strategy) {
data.getTypeMap(strategy);
}
private static void tableSharedBy(Key key, ImmutableType type1, ImmutableType type2, List