java - 如果我使用 myBatis 从几乎相同的表中提取时如何删除大量重复代码

标签 java orm mybatis spring-mybatis

我使用 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 开始)没有优雅的方法来解决这个问题。

您可以使用下面描述的技术消除一些重复,但是

  1. 并非全部
  2. 以代码复杂度为代价。

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 映射器的客户端中使用 CrudRepositoryCrudRepository 提供基本的 CRUD 和基于方法签名的附加自动生成方法。有关更多详细信息,请参阅 spring-data documentation .

关于java - 如果我使用 myBatis 从几乎相同的表中提取时如何删除大量重复代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52912996/

相关文章:

java - 使用BeanUtils.copyProperties从Mybatis创建的对象到Spring Bean,Spring bean中的属性为null。为什么?

java - 使用列表参数调用存储过程

java - 获取ManyToMany映射表的id

.net - Entity Framework /MVC3 : temporarily disable validation

java - 使用 Java/sql 存储和检索数据时遇到问题

java - JPA 和 Hibernate 有什么区别?

java - 对于这种情况,如何在 hibernate 中定义唯一约束?

java - MyBatis:递归同时映射多个相同类型的关联/集合

java - 暂时跳过 hibernate 验证

java - 实现java对象之间通信的正确方法是什么?