我使用 myBatis 来处理 myMysql。我有几个相同的表( Actor 、制作人、 Composer 等),仅包含两个字段 - id 和 name。
我必须编写很多几乎相同的代码才能处理这个问题。例如映射器
<?xml version = "1.0" encoding = "UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="ru.common.mapper.NamedEntityMapper">
<resultMap id="actor" type="Actor">
<id property="id" column="id"/>
<result property="name" column="name"/>
</resultMap>
<select id="getActorByName" parameterType="String" resultMap="actor">
SELECT * FROM actors WHERE name = #{name}
</select>
<select id="getActorById" parameterType="String" resultMap="actor">
SELECT * FROM actors WHERE id = #{id}
</select>
<insert id="saveActor" useGeneratedKeys="true" parameterType="Actor" keyProperty="id">
INSERT INTO actors (name) VALUES (#{name})
</insert>
<resultMap id="writer" type="Writer">
<id property="id" column="id"/>
<result property="name" column="name"/>
</resultMap>
<select id="getWriterByName" parameterType="String" resultMap="writer">
SELECT * FROM writers WHERE name = #{name}
</select>
<select id="getWriterById" parameterType="String" resultMap="writer">
SELECT * FROM writers WHERE id = #{id}
</select>
<insert id="saveWriter" useGeneratedKeys="true" parameterType="Writer" keyProperty="id">
INSERT INTO writers (name) VALUES (#{name})
</insert>
</mapper>
从映射器中可以看出,方法和查询非常相似,仅在表名称和返回的类型上有所不同。事实上,这样的方法还有很多,而且看起来很糟糕。
它是一个界面
public interface NamedEntityMapper {
Actor getActorById(long id);
Actor getActorByName(String name);
void saveActor(Actor actor);
Writer getWriterById(long id);
Writer getWriterByName(String name);
void saveWriter(Writer writer);
}
我尝试这样做,我为每个相似的模型制作了一个通用的界面。 (基础模型)
public interface BaseModel {
int getId();
void setId(int id);
String getName();
void setName(String name);
}
并在所有像 Actor 这样使用的模型中实现了这个接口(interface)...
但这导致了失败,因为不清楚如何向映射器解释以创建所需类的实例。如何传输需要在 xml 映射器中创建的类型?
类似的事情
public interface NamedEntityMapper<T extends BaseModel> {
T getEntityById(long id, String tableName, Class clazz);
}
和 xml 映射器
<mapper namespace="ru.common.mapper.NamedEntityMapper">
<resultMap id="entity" type="${clazz}">
<id property="id" column="id"/>
<result property="name" column="name"/>
</resultMap>
<select id="getEntityById" parameterType="String" resultMap="entity">
SELECT * FROM ${tableName} WHERE id = #{id}
</select>
</mapper>
但是我无法将返回类型作为参数传递给映射器。这可以做到吗?在我的例子中,这将允许删除大量重复的代码。
最佳答案
Mybatis(从版本 3.3.5 开始)没有优雅的方法来解决这个问题。
您可以使用下面描述的技术消除一些重复,但是
- 并非全部
- 以代码复杂度为代价。
CrudMapper
您可以尝试(在某种程度上)通过定义通用映射器来消除映射器接口(interface)中的重复:
interface CrudMapper<T> {
T getById(int id);
T getByName(String name);
void create(T);
void update(T);
}
然后用它来为实体定义单独的映射器:
interface AuthorMapper extends CrudMapper<Author> {}
interface WriterMapper extends CrudMapper<Writer> {}
带有 xml 的鉴别器
您还可以尝试使用discriminator重用结果图:
<resultMap id="workerResult" type="Worker">
<id property="id" column="id" />
<result property="name" column="name"/>
<discriminator javaType="string" column="worker_type">
<case value="author" resultType="Author"/>
<case value="writer" resultType="Writer"/>
</discriminator>
</resultMap>
但它需要使查询复杂化,即将新列 worker_type
添加到每个选择查询:
<select id="getByName" parameterType="String" resultMap="workerResult">
SELECT 'actor' as worker_type, id, name FROM actors WHERE name = #{name}
</select>
不幸的是,没有办法避免在 xml 映射器中创建单独的方法。您唯一可以做的就是使用速度宏在一个地方(即在速度宏中)进行查询。在这种情况下,方法可以如下所示:
<select id="getByName" parameterType="String" resultMap="workerResult">
#select_by_name('actor')
</select>
宏将是:
#macro(select_by_name $worker_table)
SELECT '${worker_table}' as worker_type, id, name FROM ${worker_table}s WHERE name = @name
Java API 中的鉴别器
Java API 在这方面可能更好,但也有其自身的缺点。
public interface HierarchyMapper<T> {
@SelectProvider(method = "buildGetByName", type = HierarchySqlBuilder.class)
@Results(id = "workerResult", value = {
@Result(property = "id", column = "id", id = true),
@Result(property = "name", column = "name")
})
@TypeDiscriminator(cases = {
@Case(type = Actor.class, value = "actor"),
@Case(type = Writer.class, value = "writer")},
column = "worker_type")
T getByName(@Param("name") String name, @Param("table") String table);
}
@Mapper
public interface ActorMapper extends HierarchyMapper<Actor> {
}
public class HierarchySqlBuilder {
public static String buildGetByName(
@Param("name") String name, @Param("table") String table) {
return String.format(
"SELECT '%s' as worker_type, id, name from %s where name = #{name}", table, table);
}
}
不幸的是,我不知道如何避免将table
传递到映射器中。这里的问题是,在这种情况下我们需要构建动态查询,并且实体类型(或表)是参数。调度应该发生在某个地方。一种方法是在映射器之上有一个存储库层,它将像这样执行此调度:
class WorkerRepository {
@Autowired ActorMapper actorMapper;
@Autowired WriterMapper writerMapper;
public Actor getActorByName(String name) {
return actorMapper.getByNae(name, 'actor');
}
public Writer getWriterByName(String name) {
return writerMapper.getByNae(name, 'writer');
}
}
您可能需要重新考虑该问题。鉴于所有类都具有相同的字段,您可以将所有数据存储在一个表中,并在该表中具有像 worker_type
这样的鉴别器列,以了解对象的实际类型。在这种情况下,您可以完全避免该问题,因为您有一个表,但仍然可以在 Java 中获取不同的类(可能具有共同的父级)。
spring-data-mybatis
您可以尝试的一件事是 spring-data-mybatis 。它允许注释实体:
@Entity
class Actor extends LongId {
private String name;
// getters and setters
}
@Entity
class Writer extends LongId {
private String name;
// getters and setters
}
然后定义存储库类,这些类基本上是 Spring 数据存储库:
public interface AuthorRepository extends CrudRepository<Author, Long> {
List<Author> findByName(String name);
}
public interface WriterRepository extends CrudRepository<Writer, Long> {
List<Writer> findByName(String name);
}
在这种情况下,您根本不需要手动创建映射器,而是在之前使用 mybatis 映射器的客户端中使用 CrudRepository
。
CrudRepository
提供基本的 CRUD 和基于方法签名的附加自动生成方法。有关更多详细信息,请参阅 spring-data documentation .
关于java - 如果我使用 myBatis 从几乎相同的表中提取时如何删除大量重复代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52912996/