Mybatis Generator:分离出“自动生成”和“手动编辑文件”的最佳方法是什么

时间:2013-11-01 02:59:46

标签: mybatis mybatis-generator

我正在使用 Mybatis (用于持久化java到数据库)和 Mybatis Generator 的项目(从数据库自动生成mapper xml文件和java接口)模式)。

Mybatis生成器可以很好地生成基本crud操作所需的文件。

上下文

对于某些表/类,我们需要比MyBatis Generator工具生成的“crud stuff”更多的“东西”(代码查询等)。

有没有办法拥有“两全其美”,即使用自动生成以及“自定义代码”。如何分离和构建“手动编辑的文件”和“自动生成的文件”。

提案

我正在考虑以下内容,即表格“Foo”

自动生成

  • FooCrudMapper.xml
  • interface FooCrud.java

(其中“Crud”代表“创建读取更新删除”)

手工编辑

  • FooMapper.xml
  • 界面Foo扩展了FooCrud

概念:如果架构发生了变化,您可以随时安全地自动生成“Crud”xml和.java文件,而不会消除任何自定义更改。

问题

  • mybatis能否正确处理这种情况,即此映射器是否会正确执行自动生成的“crud code”?

    FooMapper fooMapper = sqlSession.getMapper(FooMapper.class);

  • 您推荐什么方法?

编辑1: *我们的数据库设计使用“核心表”(“元素”),其他表“扩展”该表并添加额外的属性(共享密钥)。我查看了文档和来源得出的结论是,我不能将Mybatis Generator与这种“扩展”结合使用而不需要任何手动编辑:

即。这不起作用。  -ElementMapper扩展“ElementCrudMapper”  -FooMapper.xml扩展了“ElementCrudMapper”和“FooCrudMapper”

谢谢大家!

4 个答案:

答案 0 :(得分:21)

我可以分离生成的文件和手工编辑的文件。

我使用mybatis-spring和spring来管理dao接口。这个库允许MyBatis参与Spring事务,负责构建MyBatis映射器和SqlSessions并将它们注入其他bean,将MyBatis异常转换为Spring DataAccessExceptions,最后,它允许您构建您的应用程序代码,不依赖于MyBatis,Spring或MyBatis-Spring

对于DAO接口,我编写了一个通用的MybatisBaseDao来表示mybatis生成器生成的基接口。

    public interface MybatisBaseDao<T, PK extends Serializable, E> {
    int countByExample(E example);

    int deleteByExample(E example);

    int deleteByPrimaryKey(PK id);

    int insert(T record);

    int insertSelective(T record);

    List<T> selectByExample(E example);

    T selectByPrimaryKey(PK id);

    int updateByExampleSelective(@Param("record") T record, @Param("example") E example);

    int updateByExample(@Param("record") T record, @Param("example") E example);

    int updateByPrimaryKeySelective(T record);

    int updateByPrimaryKey(T record);
    }

当然,您可以根据自己的需求自定义BaseDao。例如,我们有一个UserDao,那么您可以像这样定义它

public interface UserDao extends MybatisBaseDao<User, Integer, UserExample>{
    List<User> selectUserByAddress(String address); // hand edited query method
}

对于mapper xml文件,我在mapper(.xml)基本文件夹中创建了两个包,用于分隔生成的文件和手工编辑的文件。对于上面的UserDao,我将生成器生成的UserMapper.xml放在名为'generated'的包中。我把所有手写mapper sqls放到名为manual的包中的另一个UserMapper.xml文件中。两个映射器文件以相同的标头<mapper namespace="com.xxx.dao.UserDao" >开头。 Mybatis可以扫描xml映射文件,自动映射sql和相应的接口方法。

对于生成的实体和示例对象,我直接覆盖它们。

我希望上面的方法可以帮到你!

答案 1 :(得分:2)

Larry.Z解决方案帮助我解决了相同的问题,将自动生成的手动编辑文件分开。我的项目中有一个自定义文件夹结构,并在我的项目中使用Larry解决方案,并通过使用Larry解决方案来添加此答案以帮助其他人。

最佳解决方案是向Mybatis生成器添加功能( MBG )以集成手动修改的xml映射器。 MBG必须添加解析功能以将相应的手节点方法添加到客户端映射器接口,但是现在这个功能不存在所以我使用并调整了Larry.Z解决方案。

在我的项目中,我使用:

<properties>  
...  
<java.version>1.7</java.version>  
<spring.version>3.2.2.RELEASE</spring.version>  
<mybatis.version>3.2.2</mybatis.version>  
<mybatis-spring.version>1.2.0</mybatis-spring.version>  
<mybatis-generator-core.version>1.3.2</mybatis-generator-core.version>  
...  
</properties>  

我的文件夹结构是:

<base>/dao/:MBG生成了dao类

<base>/dao/extended/:扩展生成的类(<DaoGeneratedName>Extended

<base>/sqlmap/:MBG生成客户端接口和相应的xml映射器

<base>/sqlmap/extended/
手xml映射器和手客户端接口
<InterfaceGenerated>Extended extends InterfaceGenerated {...

<base>/sqlmap/generated/:MBG生成的映射器名称空间的副本已更改

我配置了Mybatis - spring

<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer"  
        p:basePackage="<base>.sqlmap"  
        p:sqlSessionTemplate-ref="sqlSessionTemplate"  
        p:nameGenerator-ref="myBeanNameGenerator"  
    />  

只有在需要像我这样的自定义名称时才实现myBeanNameGenerator。在此示例中,您可以删除行p:nameGenerator-ref="myBeanNameGenerator"

如果您的所有客户端界面都已扩展,您可以替换上面的内容 p:basePackage="<base>.sqlmap.extended"

(我的项目配置很大,所以我提取了最重要的一点)

这是我的客户端界面和映射器手动编码的示例:

import <base>.dao.Countries;
import <base>.sqlmap.CountriesMapper;
import org.apache.ibatis.annotations.Param;

public interface CountriesMapperExtended extends CountriesMapper {

/**
 *
 * @param code
 * @return
 */
Countries selectByCountryCode(@Param("code") String code);

}

其中CountriesMapper是MBG生成的客户端界面

手动编码的相关xml映射器是:

<?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="<base>.sqlmap.extended.CountriesMapperExtended">

  <select id="selectByCountryCode" parameterType="java.lang.String" resultMap="BaseResultMap">
    select 
    <include refid="Base_Column_List" />
    from countries co
    where co.countrycode = #{code,jdbcType=VARCHAR}
  </select>

</mapper>

为了完成所有工作,我必须在xml mapper中集成所有生成的接口方法MBG,为此,我在<base>/sqlmap/generated/中复制了MBG生成的xml映射器并更改了其命名空间:

<?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="<base>.sqlmap.extended.CountriesMapperExtended">
... unchanged ...
</mapper>  

当db更改时,问题就出现了,我必须使用MBG来反映新的db结构。

所以我快速创建了一个在<base>/sqlmap/extended/中观看的bash脚本,并检查是否有手动编码的xml映射器。如果有手动编码的xml映射器,则复制相应的MBG生成更改其命名空间。

所有这些代码都不是优雅的解决方案,但有效。

bash脚本覆盖<base>/sqlmap/generated/中的文件,因此,不要在此文件夹中放入您的代码。

制作项目的备份副本并修改bash脚本,对其进行自定义并使用您的职责。

#!/bin/bash
CURDIR="$(pwd)"
SCRIPT_DIR=`dirname $0`
usage()
{
cat << EOF
usage: $0 options

This script is usefull to generate xml map to extend mybatis 
generator client interfaces. It suppose this structure:
<base>/sqlmap/           : generated xml mapper and interfaces
<base>/sqlmap/extended/  : extended xml mapper and interfaces
<base>/sqlmap/generated/ : copy of generated xml mapper changing
                           its namespace

If exist a mapper xml in <base>/sqlmap/extend identify by a name 
ending in Extended this script generate a copy of original generated
xml map of extended interface changing then namespace to reflect the 
extended Interface in <base>/sqlmap/generated.

This script require a list of base path:
$0 path1 path2 ...

Required parameters are marked by an *

OPTIONS:
  -h, --help          Show this message

EOF
}

declare -a BASES
let INDEX=0
TEMP=`getopt -o "hb:" --long "help,base:" -n "$0" -- "$@"`
eval set -- "$TEMP"
while true ; do
    case "$1" in
        -h|--help) 
            usage
            exit 1 ;;
        --) 
            shift ;
            break ;;
        *) 
            echo "Too mutch parametes!!! abort." ;
            exit 1 ;;
    esac
done
#process all paths
let INDEX=0
BASE="$1"
while [ "${BASE:0:1}" == "/" ]
do
    shift ;
    BASES[$INDEX]="$BASE"
    let INDEX+=1
    BASE="$1"
done
if [ "$INDEX" -le "0" ]
then
    echo "--bases options cannot be emplty"
    usage
    exit 1
fi

for BASE in ${BASES[@]}
do
    if [ ! -d "$BASE" ]
    then
        echo "Error: every base parameter must be a folder!!"
        echo "Base=$BASE is not a folder"
        usage
        exit 1
    fi
    SQLMAP="$BASE/sqlmap"
    if [ ! -d "$SQLMAP" ]
    then
        echo "Error: every base parameter must have a sqlmap folder!!"
        echo "$SQLMAP is not a folder"
        usage
        exit 1
    fi
    EXTENDED="$BASE/sqlmap/extended"
    if [ ! -d "$EXTENDED" ]
    then
        echo "Error: every base parameter must have a sqlmap/extended folder!!"
        echo "$EXTENDED is not a folder"
        usage
        exit 1
    fi
    GENERATED="$BASE/sqlmap/generated"
    if [ ! -d "$GENERATED" ]
    then
        mkdir -p "$GENERATED"
    fi
    while IFS= read -r -d '' file
    do
        name="${file##*/}" 
        #path="${file%/*}"
        ext=".${name##*.}"
        nameNoSuffix="${name%$ext}"
        nameBase="${nameNoSuffix%Extended}"
        sed -r 's/<mapper namespace="(.+)\.([^."]+)"\s*>\s*$/<mapper namespace="\1.extended.\2Extended">/' "$SQLMAP/$nameBase.xml" > "$GENERATED/$nameNoSuffix.xml"
    done < <(eval "find $EXTENDED/ -type f -name \*Extended\.xml -print0")

done
exit 0

使用脚本

$ ./post-generator.sh "/home/...<base>"不要将最后/放在路径上

此路径是包含sqlmap,sqlmap / extended,sqlmap / generated

的文件夹的路径

如果您像我一样拥有多个

,则可以使用路径列表

要通过maven使用它,我在项目pom.xml中使用这个插件:

            <plugin>
                <artifactId>exec-maven-plugin</artifactId>
                <groupId>org.codehaus.mojo</groupId>
                <version>1.2.1</version>
                <executions>
                    <execution>
                        <id>build client extended xml</id>
                        <goals>
                            <goal>exec</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <executable>${basedir}/scripts/post-generator.sh</executable>
                    <workingDirectory>${basedir}/scripts</workingDirectory>
                    <arguments>
                        <argument>${basedir}/<basepath1></argument>
                        <argument>${basedir}/<basepath2></argument>
                    </arguments>
                </configuration>
            </plugin>

在项目文件夹中,您可以使用$ mvn exec:exec$ mvn mybatis-generator:generate exec:exec

如果您使用Netbeans,则可以配置项目操作以在不离开Netbeans的情况下运行这些目标mybatis-generator:generate exec:exec。当db结构发生变化时,您可以手动启动它。

现在你可以毫无问题地使用exended mapper,如果db结构发生变化,让MBG完成他的工作。

在你的bean中,你可以注入具有
的扩展接口 自动生成的MBG方法加上您的手工编码方法:

    <bean id="service" class="<base>.services.ServiceImpl" scope="singleton" 
        ...
        p:countriesMapper-ref="countriesMapperExtended"
        ...
        p:sqlSessionTemplate-ref="sqlSessionTemplate"
    />

其中countriesMapperExtended bean由上面的mapperScanner生成。

答案 2 :(得分:0)

我已经给出了一个有效的答案,但由于配置庞大,它很复杂且不易理解。

现在我找到了一个更好,更简洁的答案 我的灵感来自Emacarron的帖子:Fix #35

我使用了mbg并在generatorConfig.xml中我将<javaClientGenerator type="XMLMAPPER" ...>放在文件夹映射器上生成java接口和xml映射配置。

所以在我的mapper文件夹示例中我有:

  • AnagraficaMapper.java
  • AnafigraficaMapper.xml

在模型文件夹中我有

  • Anagrafica.java
  • AnagraficaKey.java
  • AnagraficaExample.java

前两个是对象模型,扩展它们是微不足道的 要扩展映射器,我只需复制并清空
AnagraficaMapper.java - &gt; AnagraficaExMapper.java
AnagraficaMapper.xml - &gt; AnagraficaExMapper.xml
在这两个新文件中我放了我的新代码 举个例子我决定添加一个新的sql selectByPrimaryKeyMy

public interface AnagraficaExMapper extends AnagraficaMapper {
    Anagrafica selectByPrimaryKeyMy(AnagraficaKey key);
}  

这是我的界面扩展mgb生成的AnagraficaMapper界面 在AnagraficaExMapper.xml中

<?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="net.algoritmica.ciaomondo.mapper.AnagraficaExMapper" >
  <select id="selectByPrimaryKeyMy" parameterType="net.algoritmica.ciaomondo.model.AnagraficaKey" resultMap="net.algoritmica.ciaomondo.mapper.AnagraficaMapper.BaseResultMap">
    select 
      <include refid="net.algoritmica.ciaomondo.mapper.AnagraficaMapper.Base_Column_List" />
    from ANAGRAFICA anag
    where anag.IDANAGRAFICA = #{idanagrafica,jdbcType=INTEGER}
  </select>
</mapper>  

如何看待名称空间是...... AnagraficaExMapper指向新的扩展接口。

通过Fix #35上的解决方案,当MyBatis在AnagraficaExMapper.java中搜索代码并找到selectByPrimaryKeyMy方法时,它也是在AnagraficaExMapper.xml中创建的;

但是当搜索像selectByPrimaryKey这样的层次结构方法时,这在AnagraficaExMapper.xml中找不到,但是感谢Fix #35,代码也在父名称上搜索,绑定旧AnagraficaMapper.xml中的所有扩展干涉方法

要包含旧xml文件中包含的片段,您必须使用旧xml文件的完整路径,如:<include refid="net.algoritmica.ciaomondo.mapper.AnagraficaMapper.Base_Column_List" />

现在您只需将MyBatis配置为自动映射器扫描以及所有正确绑定到xml映射器的接口。
当您使用mbg感觉数据库更改时,接口重新生成但新的扩展接口未被覆盖,因此您的代码将被保存。

问候

答案 3 :(得分:0)

我在Spring Boot项目中有此任务,下面得到解决

enter image description here

在mybatis / *。xml文件中,我将生成的<mapper namespace="news.project.demo.mappers.BrandMapper">更改为 <mapper namespace="news.project.demo.mappers.extended.BrandMapperExtended"> 但是所有的sql逻辑必须写在xml文件中。接口只是没有@Select或@ResultMap批注的声明,因此您必须正确配置mybatis-generator。

在我拥有的application.properties中

mybatis.mapper-locations=classpath*:mybatis/*.xml mybatis.type-aliases-package=news.project.demo.models