feat: add @RelationManyToMany@RelationManyToOne@RelationOneToMany@RelationOneToOne annotations

This commit is contained in:
开源海哥 2023-07-06 18:07:21 +08:00
parent d556ded57e
commit 435248c56b
22 changed files with 1405 additions and 4 deletions

View File

@ -0,0 +1,37 @@
/*
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mybatisflex.annotation;
import java.lang.annotation.*;
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface RelationManyToMany {
String selfField();
String targetField();
String joinTable();
String joinSelfColumn();
String joinTargetColumn();
String orderBy() default "";
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mybatisflex.annotation;
import java.lang.annotation.*;
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface RelationManyToOne {
String selfField();
String targetField();
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mybatisflex.annotation;
import java.lang.annotation.*;
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface RelationOneToMany {
String selfField();
String targetField();
String orderBy() default "";
int limit() default 0;
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mybatisflex.annotation;
import java.lang.annotation.*;
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface RelationOneToOne {
String selfField();
String targetField();
}

View File

@ -426,6 +426,58 @@ public interface BaseMapper<T> {
return MapperUtil.getSelectOneResult(selectListByQueryAs(queryWrapper, asType)); return MapperUtil.getSelectOneResult(selectListByQueryAs(queryWrapper, asType));
} }
/**
* 根据 map 构建的条件来查询数据
*
* @param map where 条件
* @return entity 数据
*/
default T selectOneWithRelationsByMap(Map<String, Object> map) {
if (map == null || map.isEmpty()) {
throw FlexExceptions.wrap("map can not be null or empty.");
}
return selectOneWithRelationsByQuery(QueryWrapper.create().where(map).limit(1));
}
/**
* 根据 condition 来查询数据
*
* @param condition 条件
* @return 1 条数据
*/
default T selectOneWithRelationsByCondition(QueryCondition condition) {
if (condition == null) {
throw FlexExceptions.wrap("condition can not be null.");
}
return selectOneWithRelationsByQuery(QueryWrapper.create().where(condition).limit(1));
}
/**
* 根据 queryWrapper 构建的条件来查询 1 条数据
*
* @param queryWrapper query 条件
* @return entity 数据
*/
default T selectOneWithRelationsByQuery(QueryWrapper queryWrapper) {
return MapperUtil.queryRelations(this, MapperUtil.getSelectOneResult(selectListByQuery(queryWrapper)));
}
/**
* 根据 queryWrapper 构建的条件来查询 1 条数据
*
* @param queryWrapper query 条件
* @param asType 接收类型
* @return 数据内容
*/
default <R> R selectOneWithRelationsByQueryAs(QueryWrapper queryWrapper, Class<R> asType) {
return MapperUtil.queryRelations(this, MapperUtil.getSelectOneResult(selectListByQueryAs(queryWrapper, asType)));
}
/** /**
* 根据多个主键来查询多条数据 * 根据多个主键来查询多条数据
* *
@ -552,7 +604,7 @@ public interface BaseMapper<T> {
return selectObjectListByQueryAs(queryWrapper, asType); return selectObjectListByQueryAs(queryWrapper, asType);
} }
if(Map.class.isAssignableFrom(asType)){ if (Map.class.isAssignableFrom(asType)) {
return (List<R>) selectRowsByQuery(queryWrapper); return (List<R>) selectRowsByQuery(queryWrapper);
} }
@ -565,6 +617,14 @@ public interface BaseMapper<T> {
} }
/**
* 根据 query 来构建条件查询数据列表要求返回的数据为 asType
*
* @param queryWrapper 查询条件
* @param asType 接收的数据类型
* @param consumers 字段查询
* @return 数据列表
*/
default <R> List<R> selectListByQueryAs(QueryWrapper queryWrapper, Class<R> asType default <R> List<R> selectListByQueryAs(QueryWrapper queryWrapper, Class<R> asType
, Consumer<FieldQueryBuilder<R>>... consumers) { , Consumer<FieldQueryBuilder<R>>... consumers) {
List<R> list = selectListByQueryAs(queryWrapper, asType); List<R> list = selectListByQueryAs(queryWrapper, asType);
@ -577,6 +637,63 @@ public interface BaseMapper<T> {
} }
/**
* 查询 entity 及其 relation 注解字段
*
* @param queryWrapper 查询条件
*/
default List<T> selectListWithRelationsByQuery(QueryWrapper queryWrapper) {
return MapperUtil.queryRelations(this, selectListByQuery(queryWrapper));
}
/**
* 查询 entity 及其 relation 注解字段
*
* @param queryWrapper 查询条件
* @param asType 要求返回的数据类型
* @return 数据列表
*/
default <R> List<R> selectListWithRelationsByQueryAs(QueryWrapper queryWrapper, Class<R> asType) {
if (Number.class.isAssignableFrom(asType)
|| String.class == asType) {
return selectObjectListByQueryAs(queryWrapper, asType);
}
if (Map.class.isAssignableFrom(asType)) {
return (List<R>) selectRowsByQuery(queryWrapper);
}
try {
MappedStatementTypes.setCurrentType(asType);
return MapperUtil.queryRelations(this, (List<R>) selectListByQuery(queryWrapper));
} finally {
MappedStatementTypes.clear();
}
}
/**
* 查询 entity 及其 relation 注解字段
*
* @param queryWrapper 查询条件
* @param asType 返回的类型
* @param consumers 字段查询
* @return 数据列表
*/
default <R> List<R> selectListWithRelationsByQueryAs(QueryWrapper queryWrapper, Class<R> asType
, Consumer<FieldQueryBuilder<R>>... consumers) {
List<R> list = selectListByQueryAs(queryWrapper, asType);
if (list == null || list.isEmpty()) {
return Collections.emptyList();
} else {
MapperUtil.queryRelations(this, list);
MapperUtil.queryFields(this, list, consumers);
return list;
}
}
/** /**
* 查询全部数据 * 查询全部数据
* *
@ -587,6 +704,16 @@ public interface BaseMapper<T> {
} }
/**
* 查询全部数据及其 relation 字段内容
*
* @return 数据列表
*/
default List<T> selectAllWithRelations() {
return MapperUtil.queryRelations(this, selectListByQuery(new QueryWrapper()));
}
/** /**
* 根据 queryWrapper 1 条数据 * 根据 queryWrapper 1 条数据
* queryWrapper 执行的结果应该只有 1 例如 QueryWrapper.create().select(ACCOUNT.id).where... * queryWrapper 执行的结果应该只有 1 例如 QueryWrapper.create().select(ACCOUNT.id).where...

View File

@ -151,7 +151,7 @@ public class FlexConfiguration extends Configuration {
Class<?> asType = MappedStatementTypes.getCurrentType(); Class<?> asType = MappedStatementTypes.getCurrentType();
if (asType != null) { if (asType != null) {
return MapUtil.computeIfAbsent(dynamicMappedStatementCache, id + ":" + asType.getName(), return MapUtil.computeIfAbsent(dynamicMappedStatementCache, id + ":" + asType.getName(),
aClass -> replaceResultMap(ms, TableInfoFactory.ofEntityClass(asType)) clazz -> replaceResultMap(ms, TableInfoFactory.ofEntityClass(asType))
); );
} }

View File

@ -0,0 +1,135 @@
/*
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mybatisflex.core.relation;
import com.mybatisflex.annotation.RelationManyToMany;
import com.mybatisflex.core.BaseMapper;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.row.Row;
import com.mybatisflex.core.util.ClassUtil;
import com.mybatisflex.core.util.CollectionUtil;
import com.mybatisflex.core.util.MapperUtil;
import com.mybatisflex.core.util.StringUtil;
import java.lang.reflect.Field;
import java.util.*;
import static com.mybatisflex.core.query.QueryMethods.column;
class ManyToMany<SelfEntity> extends Relation<SelfEntity> {
private String joinTable;
private String joinSelfColumn;
private String joinTargetColumn;
private String orderBy;
public ManyToMany(RelationManyToMany annotation, Class<SelfEntity> entityClass, Field relationField) {
super(annotation.selfField(), annotation.targetField(), entityClass, relationField);
this.joinTable = annotation.joinTable();
this.joinSelfColumn = annotation.joinSelfColumn();
this.joinTargetColumn = annotation.joinTargetColumn();
this.orderBy = annotation.orderBy();
}
@Override
public Class<?> getMappingType() {
return Row.class;
}
@Override
public QueryWrapper toQueryWrapper(List<SelfEntity> selfEntities) {
Set<Object> selfFieldValues = getSelfFieldValues(selfEntities);
if (selfFieldValues.isEmpty()) {
return null;
}
QueryWrapper queryWrapper = QueryWrapper.create().select()
.from(joinTable);
if (selfFieldValues.size() > 1) {
queryWrapper.where(column(joinSelfColumn).in(selfFieldValues));
} else {
queryWrapper.where(column(joinSelfColumn).eq(selfFieldValues.iterator().next()));
}
return queryWrapper;
}
@Override
public void map(List<SelfEntity> selfEntities, List<?> mappingObjectList, BaseMapper<?> mapper) {
List<Row> mappingRows = (List<Row>) mappingObjectList;
Set<Object> targetValues = new LinkedHashSet<>();
for (Row row : mappingRows) {
Object targetValue = row.getIgnoreCase(joinTargetColumn);
if (targetValue != null) {
targetValues.add(targetValue);
}
}
if (targetValues.isEmpty()) {
return;
}
QueryWrapper queryWrapper = QueryWrapper.create().select()
.from(targetTableInfo.getTableNameWithSchema());
if (targetValues.size() > 1) {
queryWrapper.where(column(targetTableInfo.getColumnByProperty(targetField.getName())).in(targetValues));
} else {
queryWrapper.where(column(targetTableInfo.getColumnByProperty(targetField.getName())).eq(targetValues.iterator().next()));
}
if (StringUtil.isNotBlank(orderBy)) {
queryWrapper.orderBy(orderBy);
}
List<?> targetObjectList = mapper.selectListByQueryAs(queryWrapper, relationFieldWrapper.getMappingType());
if (CollectionUtil.isNotEmpty(targetObjectList)) {
selfEntities.forEach(selfEntity -> {
Object selfValue = selfFieldWrapper.get(selfEntity);
if (selfValue != null) {
selfValue = selfValue.toString();
Set<String> targetMappingValues = new HashSet<>();
for (Row mappingRow : mappingRows) {
if (selfValue.equals(String.valueOf(mappingRow.getIgnoreCase(joinSelfColumn)))) {
Object joinValue = mappingRow.getIgnoreCase(joinTargetColumn);
if (joinValue != null) {
targetMappingValues.add(joinValue.toString());
}
}
}
if (!targetMappingValues.isEmpty()) {
Class<?> wrapType = MapperUtil.getWrapType(relationFieldWrapper.getFieldType());
Collection<Object> collection = (Collection) ClassUtil.newInstance(wrapType);
for (Object targetObject : targetObjectList) {
Object targetValue = targetFieldWrapper.get(targetObject);
if (targetValue != null && targetMappingValues.contains(targetValue.toString())) {
collection.add(targetObject);
}
}
relationFieldWrapper.set(collection, selfEntity);
}
}
});
}
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mybatisflex.core.relation;
import com.mybatisflex.annotation.RelationManyToOne;
import com.mybatisflex.core.BaseMapper;
import com.mybatisflex.core.query.QueryWrapper;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Set;
import static com.mybatisflex.core.query.QueryMethods.column;
class ManyToOne<SelfEntity> extends Relation<SelfEntity> {
public ManyToOne(RelationManyToOne annotation, Class<SelfEntity> entityClass, Field relationField) {
super(annotation.selfField(), annotation.targetField(), entityClass, relationField);
}
@Override
public QueryWrapper toQueryWrapper(List<SelfEntity> selfEntities) {
Set<Object> selfFieldValues = getSelfFieldValues(selfEntities);
if (selfFieldValues.isEmpty()) {
return null;
}
QueryWrapper queryWrapper = QueryWrapper.create().select()
.from(targetTableInfo.getTableNameWithSchema());
if (selfFieldValues.size() > 1) {
queryWrapper.where(column(targetTableInfo.getColumnByProperty(targetField.getName())).in(selfFieldValues));
} else {
queryWrapper.where(column(targetTableInfo.getColumnByProperty(targetField.getName())).eq(selfFieldValues.iterator().next()));
}
return queryWrapper;
}
@Override
public void map(List<SelfEntity> selfEntities, List<?> targetObjectList, BaseMapper<?> mapper) {
selfEntities.forEach(selfEntity -> {
Object selfValue = selfFieldWrapper.get(selfEntity);
if (selfValue != null) {
for (Object targetObject : targetObjectList) {
Object targetValue = targetFieldWrapper.get(targetObject);
if (selfValue.equals(targetValue)) {
relationFieldWrapper.set(targetObject, selfEntity);
break;
}
}
}
});
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mybatisflex.core.relation;
import com.mybatisflex.annotation.RelationOneToMany;
import com.mybatisflex.core.BaseMapper;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.util.ClassUtil;
import com.mybatisflex.core.util.MapperUtil;
import com.mybatisflex.core.util.StringUtil;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import static com.mybatisflex.core.query.QueryMethods.column;
class OneToMany<SelfEntity> extends Relation<SelfEntity> {
private String orderBy;
private int limit;
public OneToMany(RelationOneToMany annotation, Class<SelfEntity> entityClass, Field relationField) {
super(annotation.selfField(), annotation.targetField(), entityClass, relationField);
this.orderBy = annotation.orderBy();
this.limit = annotation.limit();
}
@Override
public QueryWrapper toQueryWrapper(List<SelfEntity> selfEntities) {
Set<Object> selfFieldValues = getSelfFieldValues(selfEntities);
if (selfFieldValues.isEmpty()) {
return null;
}
QueryWrapper queryWrapper = QueryWrapper.create().select()
.from(targetTableInfo.getTableNameWithSchema());
if (selfFieldValues.size() > 1) {
queryWrapper.where(column(targetTableInfo.getColumnByProperty(targetField.getName())).in(selfFieldValues));
} else {
queryWrapper.where(column(targetTableInfo.getColumnByProperty(targetField.getName())).eq(selfFieldValues.iterator().next()));
}
if (StringUtil.isNotBlank(orderBy)) {
queryWrapper.orderBy(orderBy);
}
if (limit > 0) {
queryWrapper.limit(limit);
}
return queryWrapper;
}
@Override
public void map(List<SelfEntity> selfEntities, List<?> targetObjectList, BaseMapper<?> mapper) {
selfEntities.forEach(selfEntity -> {
Object selfValue = selfFieldWrapper.get(selfEntity);
if (selfValue != null) {
Class<?> wrapType = MapperUtil.getWrapType(relationFieldWrapper.getFieldType());
Collection<Object> collection = (Collection) ClassUtil.newInstance(wrapType);
for (Object targetObject : targetObjectList) {
Object targetValue = targetFieldWrapper.get(targetObject);
if (selfValue.equals(targetValue)) {
collection.add(targetObject);
}
}
relationFieldWrapper.set(collection, selfEntity);
}
});
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mybatisflex.core.relation;
import com.mybatisflex.annotation.RelationOneToOne;
import com.mybatisflex.core.BaseMapper;
import com.mybatisflex.core.query.QueryWrapper;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Set;
import static com.mybatisflex.core.query.QueryMethods.column;
class OneToOne<SelfEntity> extends Relation<SelfEntity> {
public OneToOne(RelationOneToOne annotation, Class<SelfEntity> entityClass, Field relationField) {
super(annotation.selfField(), annotation.targetField(), entityClass, relationField);
}
@Override
public QueryWrapper toQueryWrapper(List<SelfEntity> selfEntities) {
Set<Object> selfFieldValues = getSelfFieldValues(selfEntities);
if (selfFieldValues.isEmpty()) {
return null;
}
QueryWrapper queryWrapper = QueryWrapper.create().select()
.from(targetTableInfo.getTableNameWithSchema());
if (selfFieldValues.size() > 1) {
queryWrapper.where(column(targetTableInfo.getColumnByProperty(targetField.getName())).in(selfFieldValues));
} else {
queryWrapper.where(column(targetTableInfo.getColumnByProperty(targetField.getName())).eq(selfFieldValues.iterator().next()));
}
return queryWrapper;
}
@Override
public void map(List<SelfEntity> selfEntities, List<?> targetObjectList, BaseMapper<?> mapper) {
selfEntities.forEach(selfEntity -> {
Object selfValue = selfFieldWrapper.get(selfEntity);
if (selfValue != null) {
for (Object targetObject : targetObjectList) {
Object targetValue = targetFieldWrapper.get(targetObject);
if (selfValue.equals(targetValue)) {
relationFieldWrapper.set(targetObject, selfEntity);
break;
}
}
}
});
}
}

View File

@ -0,0 +1,170 @@
/*
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mybatisflex.core.relation;
import com.mybatisflex.core.BaseMapper;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.table.TableInfo;
import com.mybatisflex.core.table.TableInfoFactory;
import com.mybatisflex.core.util.ClassUtil;
import com.mybatisflex.core.util.FieldWrapper;
import org.apache.ibatis.reflection.Reflector;
import org.apache.ibatis.reflection.TypeParameterResolver;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
abstract class Relation<SelfEntity> {
protected Class<SelfEntity> selfEntityClass;
protected Field relationField;
protected FieldWrapper relationFieldWrapper;
protected Field selfField;
protected FieldWrapper selfFieldWrapper;
protected Field targetField;
protected Class<?> targetEntityClass;
protected TableInfo targetTableInfo;
protected FieldWrapper targetFieldWrapper;
public Relation(String selfField, String targetField, Class<SelfEntity> entityClass, Field relationField) {
this.selfEntityClass = entityClass;
this.relationField = relationField;
this.relationFieldWrapper = FieldWrapper.of(entityClass, relationField.getName());
this.selfField = ClassUtil.getFirstField(entityClass, field -> field.getName().equals(selfField));
this.selfFieldWrapper = FieldWrapper.of(entityClass, selfField);
Reflector reflector = new Reflector(entityClass);
Class<?> targetClass = reflector.getGetterType(relationField.getName());
if (Collection.class.isAssignableFrom(targetClass)) {
Type genericType = TypeParameterResolver.resolveFieldType(relationField, entityClass);
if (genericType instanceof ParameterizedType) {
this.targetEntityClass = (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[0];
}
} else if (targetClass.isArray()) {
this.targetEntityClass = targetClass.getComponentType();
} else {
this.targetEntityClass = targetClass;
}
this.targetField = ClassUtil.getFirstField(targetEntityClass, field -> field.getName().equals(targetField));
this.targetFieldWrapper = FieldWrapper.of(targetEntityClass, targetField);
this.targetTableInfo = TableInfoFactory.ofEntityClass(targetEntityClass);
}
public Class<SelfEntity> getSelfEntityClass() {
return selfEntityClass;
}
public void setSelfEntityClass(Class<SelfEntity> selfEntityClass) {
this.selfEntityClass = selfEntityClass;
}
public Field getRelationField() {
return relationField;
}
public void setRelationField(Field relationField) {
this.relationField = relationField;
}
public FieldWrapper getRelationFieldWrapper() {
return relationFieldWrapper;
}
public void setRelationFieldWrapper(FieldWrapper relationFieldWrapper) {
this.relationFieldWrapper = relationFieldWrapper;
}
public Field getSelfField() {
return selfField;
}
public void setSelfField(Field selfField) {
this.selfField = selfField;
}
public FieldWrapper getSelfFieldWrapper() {
return selfFieldWrapper;
}
public void setSelfFieldWrapper(FieldWrapper selfFieldWrapper) {
this.selfFieldWrapper = selfFieldWrapper;
}
public Field getTargetField() {
return targetField;
}
public void setTargetField(Field targetField) {
this.targetField = targetField;
}
public Class<?> getTargetEntityClass() {
return targetEntityClass;
}
public void setTargetEntityClass(Class<?> targetEntityClass) {
this.targetEntityClass = targetEntityClass;
}
public TableInfo getTargetTableInfo() {
return targetTableInfo;
}
public void setTargetTableInfo(TableInfo targetTableInfo) {
this.targetTableInfo = targetTableInfo;
}
public FieldWrapper getTargetFieldWrapper() {
return targetFieldWrapper;
}
public void setTargetFieldWrapper(FieldWrapper targetFieldWrapper) {
this.targetFieldWrapper = targetFieldWrapper;
}
protected Set<Object> getSelfFieldValues(List<SelfEntity> list) {
if (list == null || list.isEmpty()) {
return Collections.emptySet();
}
Set<Object> values = new LinkedHashSet<>();
list.forEach(self -> {
Object value = selfFieldWrapper.get(self);
if (value != null && !"".equals(value)) {
values.add(value);
}
});
return values;
}
public abstract QueryWrapper toQueryWrapper(List<SelfEntity> selfEntities);
public abstract void map(List<SelfEntity> selfEntities, List<?> mappedObjectList, BaseMapper<?> mapper);
public Class<?> getMappingType() {
return relationFieldWrapper.getMappingType();
}
}

View File

@ -0,0 +1,92 @@
/*
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mybatisflex.core.relation;
import com.mybatisflex.annotation.RelationManyToMany;
import com.mybatisflex.annotation.RelationManyToOne;
import com.mybatisflex.annotation.RelationOneToMany;
import com.mybatisflex.annotation.RelationOneToOne;
import com.mybatisflex.core.BaseMapper;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.util.ClassUtil;
import com.mybatisflex.core.util.CollectionUtil;
import org.apache.ibatis.util.MapUtil;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class RelationManager {
private static Map<Class<?>, List<Relation>> classRelations = new ConcurrentHashMap<>();
private static List<Relation> getRelations(Class<?> clazz) {
return MapUtil.computeIfAbsent(classRelations, clazz, RelationManager::doGetRelations);
}
private static List<Relation> doGetRelations(Class<?> entityClass) {
List<Field> allFields = ClassUtil.getAllFields(entityClass);
List<Relation> relations = new ArrayList<>();
for (Field field : allFields) {
RelationManyToMany manyToManyAnnotation = field.getAnnotation(RelationManyToMany.class);
if (manyToManyAnnotation != null) {
relations.add(new ManyToMany<>(manyToManyAnnotation, entityClass, field));
}
RelationManyToOne manyToOneAnnotation = field.getAnnotation(RelationManyToOne.class);
if (manyToOneAnnotation != null) {
relations.add(new ManyToOne<>(manyToOneAnnotation, entityClass, field));
}
RelationOneToMany oneToManyAnnotation = field.getAnnotation(RelationOneToMany.class);
if (oneToManyAnnotation != null) {
relations.add(new OneToMany<>(oneToManyAnnotation, entityClass, field));
}
RelationOneToOne oneToOneAnnotation = field.getAnnotation(RelationOneToOne.class);
if (oneToOneAnnotation != null) {
relations.add(new OneToOne<>(oneToOneAnnotation, entityClass, field));
}
}
return relations;
}
@SuppressWarnings({"rawtypes", "unchecked"})
public static <Entity> void queryRelations(BaseMapper<?> mapper, List<Entity> entities) {
if (CollectionUtil.isEmpty(entities)) {
return;
}
Class<Entity> objectClass = (Class<Entity>) entities.get(0).getClass();
List<Relation> relations = getRelations(objectClass);
if (relations.isEmpty()) {
return;
}
relations.forEach(relation -> {
QueryWrapper queryWrapper = relation.toQueryWrapper(entities);
Class<?> mappingType = relation.getMappingType();
List<?> targetObjectList = mapper.selectListByQueryAs(queryWrapper, mappingType);
if (CollectionUtil.isNotEmpty(targetObjectList)) {
relation.map(entities, targetObjectList, mapper);
}
});
}
}

View File

@ -130,6 +130,21 @@ public class Row extends LinkedHashMap<String, Object> implements UpdateWrapper
return result != null ? result : defaultValue; return result != null ? result : defaultValue;
} }
public Object getIgnoreCase(String key){
for (String innerKey : keySet()) {
if (innerKey.equalsIgnoreCase(key)){
return super.get(innerKey);
}
}
return null;
}
public Object getIgnoreCase(String key, Object defaultValue){
Object result = getIgnoreCase(key);
return result != null ? result : defaultValue;
}
@Override @Override
public Object put(String key, Object value) { public Object put(String key, Object value) {

View File

@ -26,6 +26,7 @@ public class FieldWrapper {
private Class<?> fieldType; private Class<?> fieldType;
private Class<?> mappingType; private Class<?> mappingType;
private Method getterMethod;
private Method setterMethod; private Method setterMethod;
public static FieldWrapper of(Class<?> clazz, String fieldName) { public static FieldWrapper of(Class<?> clazz, String fieldName) {
@ -49,10 +50,11 @@ public class FieldWrapper {
throw new IllegalStateException("Can not find field \"" + fieldName + "\" in class: " + clazz); throw new IllegalStateException("Can not find field \"" + fieldName + "\" in class: " + clazz);
} }
String setterName = "set" + StringUtil.firstCharToUpperCase(fieldName);
Method setter = ClassUtil.getFirstMethod(clazz, method -> Method setter = ClassUtil.getFirstMethod(clazz, method ->
method.getParameterCount() == 1 method.getParameterCount() == 1
&& Modifier.isPublic(method.getModifiers()) && Modifier.isPublic(method.getModifiers())
&& method.getName().equals("set" + StringUtil.firstCharToUpperCase(fieldName))); && method.getName().equals(setterName));
if (setter == null) { if (setter == null) {
throw new IllegalStateException("Can not find method \"set" + StringUtil.firstCharToUpperCase(fieldName) + "\" in class: " + clazz); throw new IllegalStateException("Can not find method \"set" + StringUtil.firstCharToUpperCase(fieldName) + "\" in class: " + clazz);
@ -63,6 +65,11 @@ public class FieldWrapper {
fieldWrapper.mappingType = parseMappingType(findField); fieldWrapper.mappingType = parseMappingType(findField);
fieldWrapper.setterMethod = setter; fieldWrapper.setterMethod = setter;
String[] getterNames = new String[]{"get" + StringUtil.firstCharToUpperCase(fieldName), "is" + StringUtil.firstCharToUpperCase(fieldName)};
fieldWrapper.getterMethod = ClassUtil.getFirstMethod(clazz, method -> method.getParameterCount() == 0
&& Modifier.isPublic(method.getModifiers())
&& ArrayUtil.contains(getterNames, method.getName()));
wrapperMap.put(fieldName, fieldWrapper); wrapperMap.put(fieldName, fieldWrapper);
} }
} }
@ -97,6 +104,14 @@ public class FieldWrapper {
} }
} }
public Object get(Object target) {
try {
return getterMethod.invoke(target);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Class<?> getFieldType() { public Class<?> getFieldType() {
return fieldType; return fieldType;
} }

View File

@ -21,6 +21,7 @@ import com.mybatisflex.core.exception.FlexExceptions;
import com.mybatisflex.core.field.FieldQuery; import com.mybatisflex.core.field.FieldQuery;
import com.mybatisflex.core.field.FieldQueryBuilder; import com.mybatisflex.core.field.FieldQueryBuilder;
import com.mybatisflex.core.query.*; import com.mybatisflex.core.query.*;
import com.mybatisflex.core.relation.RelationManager;
import org.apache.ibatis.exceptions.TooManyResultsException; import org.apache.ibatis.exceptions.TooManyResultsException;
import org.apache.ibatis.session.defaults.DefaultSqlSession; import org.apache.ibatis.session.defaults.DefaultSqlSession;
@ -165,7 +166,18 @@ public class MapperUtil {
} }
private static Class<?> getWrapType(Class<?> type) { public static <Entity> Entity queryRelations(BaseMapper<?> mapper, Entity entity) {
queryRelations(mapper,Collections.singletonList(entity));
return entity;
}
public static <Entity> List<Entity> queryRelations(BaseMapper<?> mapper, List<Entity> entities) {
RelationManager.queryRelations(mapper, entities);
return entities;
}
public static Class<?> getWrapType(Class<?> type) {
if (ClassUtil.canInstance(type.getModifiers())) { if (ClassUtil.canInstance(type.getModifiers())) {
return type; return type;
} }

View File

@ -0,0 +1,109 @@
/*
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mybatisflex.test.relation.onetoone;
import com.mybatisflex.annotation.*;
import java.io.Serializable;
import java.util.List;
@Table(value = "tb_account")
public class Account implements Serializable {
private static final long serialVersionUID = 1L;
@Id(keyType = KeyType.Auto)
private Long id;
private String userName;
private int age;
// @RelationOneToOne(selfField = "id", targetField = "accountId")
private IDCard idCard;
// @RelationOneToMany(selfField = "id", targetField = "accountId")
private List<Book> books;
@RelationManyToMany(
joinTable = "tb_role_mapping",
selfField = "id", joinSelfColumn = "account_id",
targetField = "id", joinTargetColumn = "role_id"
)
private List<Role> roles;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public IDCard getIdCard() {
return idCard;
}
public void setIdCard(IDCard idCard) {
this.idCard = idCard;
}
public List<Book> getBooks() {
return books;
}
public void setBooks(List<Book> books) {
this.books = books;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", userName='" + userName + '\'' +
", age=" + age +
", idCard=" + idCard +
", books=" + books +
", roles=" + roles +
'}';
}
}

View File

@ -0,0 +1,88 @@
/*
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mybatisflex.test.relation.onetoone;
import com.mybatisflex.annotation.RelationManyToOne;
import com.mybatisflex.annotation.Table;
import java.io.Serializable;
@Table(value = "tb_book")
public class Book implements Serializable {
private Long id;
private Long accountId;
private String title;
private String content;
@RelationManyToOne(selfField = "accountId",targetField = "id")
private Account account;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getAccountId() {
return accountId;
}
public void setAccountId(Long accountId) {
this.accountId = accountId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
@Override
public String toString() {
return "Book{" +
"id=" + id +
", accountId=" + accountId +
", title='" + title + '\'' +
", content='" + content + '\'' +
", account=" + account +
'}';
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mybatisflex.test.relation.onetoone;
import com.mybatisflex.annotation.Table;
import java.io.Serializable;
@Table(value = "tb_idcard")
public class IDCard implements Serializable {
private Long accountId;
private String cardNo;
private String content;
public Long getAccountId() {
return accountId;
}
public void setAccountId(Long accountId) {
this.accountId = accountId;
}
public String getCardNo() {
return cardNo;
}
public void setCardNo(String cardNo) {
this.cardNo = cardNo;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "IDCard{" +
"accountId=" + accountId +
", cardNo='" + cardNo + '\'' +
", content='" + content + '\'' +
'}';
}
}

View File

@ -0,0 +1,91 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mybatisflex.test.relation.onetoone;
import com.mybatisflex.core.MybatisFlexBootstrap;
import com.mybatisflex.core.audit.AuditManager;
import com.mybatisflex.core.audit.ConsoleMessageCollector;
import com.mybatisflex.core.audit.MessageCollector;
import com.mybatisflex.core.relation.RelationManager;
import com.mybatisflex.test.relation.mapper.AccountMapper;
import com.mybatisflex.test.relation.mapper.BookMapper;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import javax.sql.DataSource;
import java.util.List;
public class RelationsTester {
static AccountMapper accountMapper;
static BookMapper bookMapper;
@BeforeClass
public static void init() {
DataSource dataSource = new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("relation/onetoone/schema.sql")
.addScript("relation/onetoone/data.sql")
.build();
MybatisFlexBootstrap bootstrap = MybatisFlexBootstrap.getInstance()
.setDataSource(dataSource)
.addMapper(AccountMapper.class)
.addMapper(BookMapper.class)
.start();
//开启审计功能
AuditManager.setAuditEnable(true);
//设置 SQL 审计收集器
MessageCollector collector = new ConsoleMessageCollector();
AuditManager.setMessageCollector(collector);
accountMapper = bootstrap.getMapper(AccountMapper.class);
bookMapper = bootstrap.getMapper(BookMapper.class);
}
@Test
public void testOneToOne() {
List<Account> accounts = accountMapper.selectAll();
System.out.println(">>>>>>1: " + accounts);
RelationManager.queryRelations(accountMapper, accounts);
System.out.println(">>>>>>2: " + accounts);
}
@Test
public void testManyToOne() {
List<Book> books = bookMapper.selectAll();
System.out.println(">>>>>>1: " + books);
RelationManager.queryRelations(bookMapper, books);
System.out.println(">>>>>>2: " + books);
}
@Test
public void testManyToMany1() {
List<Account> accounts = accountMapper.selectAll();
System.out.println(">>>>>>1: " + accounts);
RelationManager.queryRelations(accountMapper, accounts);
System.out.println(">>>>>>2: " + accounts);
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mybatisflex.test.relation.onetoone;
import com.mybatisflex.annotation.RelationManyToMany;
import com.mybatisflex.annotation.Table;
import java.io.Serializable;
import java.util.List;
@Table(value = "tb_role")
public class Role implements Serializable {
private Long id;
private String name;
@RelationManyToMany(
joinTable = "tb_role_mapping",
selfField = "id", joinSelfColumn = "role_id",
targetField = "id", joinTargetColumn = "account_id"
)
private List<Account> accounts;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Account> getAccounts() {
return accounts;
}
public void setAccounts(List<Account> accounts) {
this.accounts = accounts;
}
@Override
public String toString() {
return "Role{" +
"id=" + id +
", name='" + name + '\'' +
", accounts=" + accounts +
'}';
}
}

View File

@ -0,0 +1,30 @@
INSERT INTO tb_account
VALUES (1, '张三', 18),
(2, '王麻子叔叔', 19);
INSERT INTO tb_idcard
VALUES (1,'0001', '内容1'),
(2,'0002', '内容2');
INSERT INTO tb_book
VALUES (1,1,'图书1', '内容1'),
(2,2,'图书2', '内容2'),
(3,1,'图书3', '内容2'),
(4,1,'图书4', '内容2'),
(5,1,'图书5', '内容2'),
(6,2,'图书6', '内容2');
INSERT INTO tb_role
VALUES (1,'角色1'),
(2,'角色2'),
(3,'角色3');
INSERT INTO tb_role_mapping
VALUES (1,1),
(1,3),
(2,1),
(2,2),
(2,3);

View File

@ -0,0 +1,35 @@
CREATE TABLE IF NOT EXISTS `tb_account`
(
`id` INTEGER auto_increment,
`user_name` VARCHAR(100),
`age` Integer
);
CREATE TABLE IF NOT EXISTS `tb_idcard`
(
`account_id` Integer,
`card_no` VARCHAR(100),
`content` text
);
CREATE TABLE IF NOT EXISTS `tb_book`
(
`id` INTEGER auto_increment,
`account_id` Integer,
`title` VARCHAR(100),
`content` text
);
CREATE TABLE IF NOT EXISTS `tb_role`
(
`id` INTEGER auto_increment,
`name` VARCHAR(100)
);
CREATE TABLE IF NOT EXISTS `tb_role_mapping`
(
`account_id` INTEGER ,
`role_id` INTEGER
);