mirror of https://github.com/halo-dev/halo
Automate attribute converter (#1325)
* Deprecate AbstractConverter * Remove unused enum and attribute converter * Add AttributeConverterApplyTest * Add JpaConfiguration * Add AttributeConverterAutoGenerator * Integrate automate-attribute-converter * Rename JpaConfiguration * Remove useless attribute converters * Exclude property enums for auto-generating * Refine JournalType definition * Fix an error about existing injected typepull/1331/head
parent
e6b32ac8c2
commit
8472a7b3d5
|
@ -10,6 +10,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
|||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
|
@ -19,6 +20,7 @@ import org.springframework.web.client.RestTemplate;
|
|||
import run.halo.app.cache.AbstractStringCacheStore;
|
||||
import run.halo.app.cache.InMemoryCacheStore;
|
||||
import run.halo.app.cache.LevelCacheStore;
|
||||
import run.halo.app.config.attributeconverter.AttributeConverterAutoGenerateConfiguration;
|
||||
import run.halo.app.config.properties.HaloProperties;
|
||||
import run.halo.app.repository.base.BaseRepositoryImpl;
|
||||
import run.halo.app.utils.HttpClientUtils;
|
||||
|
@ -35,6 +37,7 @@ import run.halo.app.utils.HttpClientUtils;
|
|||
@EnableConfigurationProperties(HaloProperties.class)
|
||||
@EnableJpaRepositories(basePackages = "run.halo.app.repository", repositoryBaseClass =
|
||||
BaseRepositoryImpl.class)
|
||||
@Import(AttributeConverterAutoGenerateConfiguration.class)
|
||||
public class HaloConfiguration {
|
||||
|
||||
private final HaloProperties haloProperties;
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package run.halo.app.config.attributeconverter;
|
||||
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryBuilderCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* Jpa configuration.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
public class AttributeConverterAutoGenerateConfiguration {
|
||||
|
||||
@Bean
|
||||
EntityManagerFactoryBuilderCustomizer entityManagerFactoryBuilderCustomizer(
|
||||
ConfigurableListableBeanFactory factory) {
|
||||
return builder -> builder.setPersistenceUnitPostProcessors(
|
||||
new AutoGenerateConverterPersistenceUnitPostProcessor(factory));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package run.halo.app.config.attributeconverter;
|
||||
|
||||
import static net.bytebuddy.description.annotation.AnnotationDescription.Builder.ofType;
|
||||
import static net.bytebuddy.description.type.TypeDescription.Generic.Builder.parameterizedType;
|
||||
import static net.bytebuddy.implementation.FieldAccessor.ofField;
|
||||
import static net.bytebuddy.implementation.MethodDelegation.to;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isDefaultConstructor;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import javax.persistence.AttributeConverter;
|
||||
import javax.persistence.Converter;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.NamingStrategy;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
|
||||
import net.bytebuddy.implementation.MethodCall;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* Attribute converter auto generator.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
class AttributeConverterAutoGenerator {
|
||||
|
||||
/**
|
||||
* Auto generation suffix.
|
||||
*/
|
||||
public static final String AUTO_GENERATION_SUFFIX = "$AttributeConverterGeneratedByByteBuddy";
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
public AttributeConverterAutoGenerator(ClassLoader classLoader) {
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
public <T> Class<?> generate(Class<T> clazz) {
|
||||
try {
|
||||
return new ByteBuddy()
|
||||
.with(new NamingStrategy.AbstractBase() {
|
||||
@Override
|
||||
protected String name(TypeDescription superClass) {
|
||||
return clazz.getName() + AUTO_GENERATION_SUFFIX;
|
||||
}
|
||||
})
|
||||
.subclass(
|
||||
parameterizedType(AttributeConverter.class, clazz, Integer.class).build())
|
||||
.annotateType(ofType(Converter.class).define("autoApply", true).build())
|
||||
.constructor(isDefaultConstructor())
|
||||
.intercept(MethodCall.invoke(Object.class.getDeclaredConstructor())
|
||||
.andThen(ofField("enumType").setsValue(clazz)))
|
||||
.defineField("enumType", Class.class, Modifier.PRIVATE | Modifier.FINAL)
|
||||
.method(named("convertToDatabaseColumn"))
|
||||
.intercept(to(AttributeConverterInterceptor.class))
|
||||
.method(named("convertToEntityAttribute"))
|
||||
.intercept(to(AttributeConverterInterceptor.class))
|
||||
.make()
|
||||
.load(this.classLoader, ClassLoadingStrategy.Default.INJECTION.allowExistingTypes())
|
||||
.getLoaded();
|
||||
} catch (NoSuchMethodException e) {
|
||||
// should never happen
|
||||
throw new RuntimeException("Failed to get declared constructor.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isGeneratedByByteBuddy(String className) {
|
||||
return StringUtils.endsWith(className, AUTO_GENERATION_SUFFIX);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package run.halo.app.config.attributeconverter;
|
||||
|
||||
import net.bytebuddy.implementation.bind.annotation.FieldValue;
|
||||
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
|
||||
import run.halo.app.model.enums.ValueEnum;
|
||||
|
||||
/**
|
||||
* Attribute Converter Interceptor.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
public class AttributeConverterInterceptor {
|
||||
|
||||
private AttributeConverterInterceptor() {
|
||||
}
|
||||
|
||||
@RuntimeType
|
||||
public static <T extends Enum<T> & ValueEnum<V>, V> V convertToDatabaseColumn(T attribute) {
|
||||
return attribute == null ? null : attribute.getValue();
|
||||
}
|
||||
|
||||
@RuntimeType
|
||||
public static <T extends Enum<T> & ValueEnum<V>, V> T convertToEntityAttribute(V dbData,
|
||||
@FieldValue("enumType") Class<T> enumType) {
|
||||
return dbData == null ? null : ValueEnum.valueToEnum(enumType, dbData);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package run.halo.app.config.attributeconverter;
|
||||
|
||||
import static java.util.stream.Collectors.toUnmodifiableSet;
|
||||
|
||||
import java.util.Set;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
import org.springframework.core.type.filter.AssignableTypeFilter;
|
||||
import org.springframework.orm.jpa.persistenceunit.MutablePersistenceUnitInfo;
|
||||
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitPostProcessor;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import run.halo.app.model.enums.ValueEnum;
|
||||
import run.halo.app.model.properties.PropertyEnum;
|
||||
|
||||
/**
|
||||
* Attribute converter persistence unit post processor.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
class AutoGenerateConverterPersistenceUnitPostProcessor implements PersistenceUnitPostProcessor {
|
||||
|
||||
private static final String PACKAGE_TO_SCAN = "run.halo.app";
|
||||
|
||||
private final ConfigurableListableBeanFactory factory;
|
||||
|
||||
public AutoGenerateConverterPersistenceUnitPostProcessor(
|
||||
ConfigurableListableBeanFactory factory) {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessPersistenceUnitInfo(MutablePersistenceUnitInfo pui) {
|
||||
var generator = new AttributeConverterAutoGenerator(factory.getBeanClassLoader());
|
||||
|
||||
findValueEnumClasses()
|
||||
.stream()
|
||||
.map(generator::generate)
|
||||
.map(Class::getName)
|
||||
.forEach(pui::addManagedClassName);
|
||||
}
|
||||
|
||||
private Set<Class<?>> findValueEnumClasses() {
|
||||
var scanner = new ClassPathScanningCandidateComponentProvider(false);
|
||||
// include ValueEnum class
|
||||
scanner.addIncludeFilter(new AssignableTypeFilter(ValueEnum.class));
|
||||
// exclude PropertyEnum class
|
||||
scanner.addExcludeFilter(new AssignableTypeFilter(PropertyEnum.class));
|
||||
|
||||
return scanner.findCandidateComponents(PACKAGE_TO_SCAN)
|
||||
.stream()
|
||||
.filter(bd -> bd.getBeanClassName() != null)
|
||||
.map(bd -> ClassUtils.resolveClassName(bd.getBeanClassName(), null))
|
||||
.collect(toUnmodifiableSet());
|
||||
}
|
||||
}
|
|
@ -47,7 +47,7 @@ public class Journal extends BaseEntity {
|
|||
private Long likes;
|
||||
|
||||
@Column(name = "type")
|
||||
@ColumnDefault("1")
|
||||
@ColumnDefault("0")
|
||||
private JournalType type;
|
||||
|
||||
@Override
|
||||
|
|
|
@ -10,12 +10,12 @@ public enum JournalType implements ValueEnum<Integer> {
|
|||
/**
|
||||
* Public type.
|
||||
*/
|
||||
PUBLIC(1),
|
||||
PUBLIC(0),
|
||||
|
||||
/**
|
||||
* Intimate type.
|
||||
*/
|
||||
INTIMATE(0);
|
||||
INTIMATE(1);
|
||||
|
||||
private final int value;
|
||||
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
package run.halo.app.model.enums;
|
||||
|
||||
/**
|
||||
* Post type.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
@Deprecated
|
||||
public enum PostType implements ValueEnum<Integer> {
|
||||
|
||||
/**
|
||||
* 普通文章
|
||||
*/
|
||||
POST(0),
|
||||
|
||||
/**
|
||||
* 自定义页面
|
||||
*/
|
||||
PAGE(1),
|
||||
|
||||
/**
|
||||
* 日志
|
||||
*/
|
||||
JOURNAL(2);
|
||||
|
||||
private final Integer value;
|
||||
|
||||
PostType(Integer value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -11,6 +11,13 @@ import org.springframework.util.Assert;
|
|||
*/
|
||||
public interface ValueEnum<T> {
|
||||
|
||||
/**
|
||||
* Gets enum value.
|
||||
*
|
||||
* @return enum value
|
||||
*/
|
||||
T getValue();
|
||||
|
||||
/**
|
||||
* Converts value to corresponding enum.
|
||||
*
|
||||
|
@ -20,7 +27,7 @@ public interface ValueEnum<T> {
|
|||
* @param <E> enum generic
|
||||
* @return corresponding enum
|
||||
*/
|
||||
static <V, E extends ValueEnum<V>> E valueToEnum(Class<E> enumType, V value) {
|
||||
static <V, E extends Enum<E> & ValueEnum<V>> E valueToEnum(Class<E> enumType, V value) {
|
||||
Assert.notNull(enumType, "enum type must not be null");
|
||||
Assert.notNull(value, "value must not be null");
|
||||
Assert.isTrue(enumType.isEnum(), "type must be an enum type");
|
||||
|
@ -30,12 +37,4 @@ public interface ValueEnum<T> {
|
|||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException("unknown database value: " + value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets enum value.
|
||||
*
|
||||
* @return enum value
|
||||
*/
|
||||
T getValue();
|
||||
|
||||
}
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
package run.halo.app.model.enums.converter;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Objects;
|
||||
import javax.persistence.AttributeConverter;
|
||||
import run.halo.app.model.enums.ValueEnum;
|
||||
import run.halo.app.utils.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Abstract converter.
|
||||
*
|
||||
* @param <E> enum generic
|
||||
* @param <V> value generic
|
||||
* @author johnniang
|
||||
* @date 12/6/18
|
||||
*/
|
||||
public abstract class AbstractConverter<E extends ValueEnum<V>, V>
|
||||
implements AttributeConverter<E, V> {
|
||||
|
||||
private final Class<E> clazz;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected AbstractConverter() {
|
||||
Type enumType = Objects.requireNonNull(
|
||||
ReflectionUtils
|
||||
.getParameterizedTypeBySuperClass(AbstractConverter.class, this.getClass())
|
||||
).getActualTypeArguments()[0];
|
||||
this.clazz = (Class<E>) enumType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V convertToDatabaseColumn(E attribute) {
|
||||
return attribute == null ? null : attribute.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public E convertToEntityAttribute(V dbData) {
|
||||
return dbData == null ? null : ValueEnum.valueToEnum(clazz, dbData);
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package run.halo.app.model.enums.converter;
|
||||
|
||||
import javax.persistence.Converter;
|
||||
import run.halo.app.model.enums.AttachmentType;
|
||||
|
||||
/**
|
||||
* Attachment type converter
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 3/27/19
|
||||
*/
|
||||
@Converter(autoApply = true)
|
||||
public class AttachmentTypeConverter extends AbstractConverter<AttachmentType, Integer> {
|
||||
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package run.halo.app.model.enums.converter;
|
||||
|
||||
import javax.persistence.Converter;
|
||||
import run.halo.app.model.enums.CommentStatus;
|
||||
|
||||
/**
|
||||
* PostComment status converter.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 3/27/19
|
||||
*/
|
||||
@Converter(autoApply = true)
|
||||
public class CommentStatusConverter extends AbstractConverter<CommentStatus, Integer> {
|
||||
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package run.halo.app.model.enums.converter;
|
||||
|
||||
import javax.persistence.Converter;
|
||||
import run.halo.app.model.enums.DataType;
|
||||
|
||||
/**
|
||||
* Data type converter.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 4/10/19
|
||||
*/
|
||||
@Converter(autoApply = true)
|
||||
public class DataTypeConverter extends AbstractConverter<DataType, Integer> {
|
||||
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package run.halo.app.model.enums.converter;
|
||||
|
||||
import javax.persistence.Converter;
|
||||
import run.halo.app.model.enums.LogType;
|
||||
|
||||
/**
|
||||
* Log type converter.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 3/27/19
|
||||
*/
|
||||
@Converter(autoApply = true)
|
||||
public class LogTypeConverter extends AbstractConverter<LogType, Integer> {
|
||||
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package run.halo.app.model.enums.converter;
|
||||
|
||||
import javax.persistence.Converter;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
|
||||
/**
|
||||
* PostStatus converter.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 3/27/19
|
||||
*/
|
||||
@Converter(autoApply = true)
|
||||
public class PostStatusConverter extends AbstractConverter<PostStatus, Integer> {
|
||||
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
package run.halo.app.model.enums.converter;
|
||||
|
||||
import javax.persistence.Converter;
|
||||
import run.halo.app.model.enums.PostType;
|
||||
|
||||
/**
|
||||
* PostType converter.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 3/27/19
|
||||
*/
|
||||
@Converter(autoApply = true)
|
||||
@Deprecated
|
||||
public class PostTypeConverter extends AbstractConverter<PostType, Integer> {
|
||||
|
||||
}
|
|
@ -281,7 +281,8 @@ public interface OptionService extends CrudService<Option, Integer> {
|
|||
* @return an optional value enum value
|
||||
*/
|
||||
@NonNull
|
||||
<V, E extends ValueEnum<V>> Optional<E> getValueEnumByProperty(@NonNull PropertyEnum property,
|
||||
<V, E extends Enum<E> & ValueEnum<V>> Optional<E> getValueEnumByProperty(
|
||||
@NonNull PropertyEnum property,
|
||||
@NonNull Class<V> valueType, @NonNull Class<E> enumType);
|
||||
|
||||
/**
|
||||
|
@ -296,7 +297,8 @@ public interface OptionService extends CrudService<Option, Integer> {
|
|||
* @return value enum value or null if the default value is null
|
||||
*/
|
||||
@Nullable
|
||||
<V, E extends ValueEnum<V>> E getValueEnumByPropertyOrDefault(@NonNull PropertyEnum property,
|
||||
<V, E extends Enum<E> & ValueEnum<V>> E getValueEnumByPropertyOrDefault(
|
||||
@NonNull PropertyEnum property,
|
||||
@NonNull Class<V> valueType, @NonNull Class<E> enumType, @Nullable E defaultValue);
|
||||
|
||||
/**
|
||||
|
|
|
@ -28,7 +28,6 @@ import org.springframework.transaction.annotation.Transactional;
|
|||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import run.halo.app.cache.AbstractStringCacheStore;
|
||||
import run.halo.app.config.properties.HaloProperties;
|
||||
import run.halo.app.event.options.OptionUpdatedEvent;
|
||||
import run.halo.app.exception.MissingPropertyException;
|
||||
import run.halo.app.model.dto.OptionDTO;
|
||||
|
@ -379,14 +378,16 @@ public class OptionServiceImpl extends AbstractCrudService<Option, Integer>
|
|||
}
|
||||
|
||||
@Override
|
||||
public <V, E extends ValueEnum<V>> Optional<E> getValueEnumByProperty(PropertyEnum property,
|
||||
public <V, E extends Enum<E> & ValueEnum<V>> Optional<E> getValueEnumByProperty(
|
||||
PropertyEnum property,
|
||||
Class<V> valueType, Class<E> enumType) {
|
||||
return getByProperty(property).map(value -> ValueEnum
|
||||
.valueToEnum(enumType, PropertyEnum.convertTo(value.toString(), valueType)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V, E extends ValueEnum<V>> E getValueEnumByPropertyOrDefault(PropertyEnum property,
|
||||
public <V, E extends Enum<E> & ValueEnum<V>> E getValueEnumByPropertyOrDefault(
|
||||
PropertyEnum property,
|
||||
Class<V> valueType, Class<E> enumType, E defaultValue) {
|
||||
return getValueEnumByProperty(property, valueType, enumType).orElse(defaultValue);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
package run.halo.app.attributeconverter;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.Query;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import run.halo.app.config.attributeconverter.AttributeConverterAutoGenerateConfiguration;
|
||||
|
||||
/**
|
||||
* Attribute converter apply result test.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
@DataJpaTest
|
||||
@Import(AttributeConverterAutoGenerateConfiguration.class)
|
||||
class AttributeConverterApplyTest {
|
||||
|
||||
@Autowired
|
||||
EntityManager entityManager;
|
||||
|
||||
@Autowired
|
||||
CityRepository cityRepository;
|
||||
|
||||
@Test
|
||||
void shouldAutoAppliedForAttributeConverter() {
|
||||
final var city = new City();
|
||||
city.setName("ChengDu");
|
||||
city.setLevel(CityLevel.CITY);
|
||||
cityRepository.save(city);
|
||||
|
||||
final var cityId = city.getId();
|
||||
|
||||
Query nativeQuery =
|
||||
entityManager.createNativeQuery("select level from city where id = " + cityId);
|
||||
Object level = nativeQuery.getSingleResult();
|
||||
Assertions.assertEquals(CityLevel.CITY.getValue(), level);
|
||||
}
|
||||
|
||||
@SpringBootApplication
|
||||
static class Application {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package run.halo.app.attributeconverter;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* City entity.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
@Entity
|
||||
@Data
|
||||
class City {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Integer id;
|
||||
|
||||
private String name;
|
||||
|
||||
private CityLevel level;
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package run.halo.app.attributeconverter;
|
||||
|
||||
import run.halo.app.model.enums.ValueEnum;
|
||||
|
||||
/**
|
||||
* City level.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
enum CityLevel implements ValueEnum<Integer> {
|
||||
|
||||
PROVINCE(123),
|
||||
|
||||
CITY(456),
|
||||
|
||||
DISTRICT(789);
|
||||
|
||||
private final int value;
|
||||
|
||||
CityLevel(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package run.halo.app.attributeconverter;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
/**
|
||||
* City operation repository.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
interface CityRepository extends JpaRepository<City, Integer> {
|
||||
}
|
Loading…
Reference in New Issue