[JAVA] Comment passer un objet à Mapper dans MyBatis sans passer par un argument

Je ne pouvais pas le trouver au moment de publier l'article, alors je l'ai recherché.

Ce que je voulais faire

Pour ceux qui veulent juste une conclusion

Exemple peut être trouvé ici. Je veux réécrire la méthode addParameter à la ligne 66 de ce code source et le transmettre. Si vous spécifiez un objet, vous pouvez y accéder de la même manière que vous l'avez passé en argument.

Description du code

Vue d'ensemble

Tout d'abord, le code source correspondant est indiqué ci-dessous.

<détails>

Code source </ summary>

CustomSqlSessionConfig.java


package mybatis_implicit_parameter.config;

import lombok.experimental.Delegate;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Proxy;
import java.util.Map;

@Configuration
public class CustomSqlSessionConfig {
    @Bean
    public SqlSessionTemplate sqlSession(SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplateExt(sqlSessionFactory);
    }

    static class SqlSessionTemplateExt extends SqlSessionTemplate {
        public SqlSessionTemplateExt(SqlSessionFactory sqlSessionFactory) {
            super(sqlSessionFactory);
        }

        Object invokeParentMethod(Method method, Object[] args) throws Throwable {
            return makeParentMethodHandle(method).bindTo(this).invokeWithArguments(args);
        }

        MethodHandle makeParentMethodHandle(Method method) throws Exception {
            return MethodHandles.lookup().findSpecial(SqlSessionTemplate.class, method.getName(),
                    MethodType.methodType(method.getReturnType(), method.getParameterTypes()), SqlSessionTemplateExt.class);
        }

        @Delegate
        final SqlSession proxy = (SqlSession)Proxy.newProxyInstance(SqlSessionTemplateExt.class.getClassLoader(),
                new Class[]{SqlSession.class}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        if(hasParameter(method)) {
                            args[1] = addParameter(args[1]);
                        }
                        return SqlSessionTemplateExt.this.invokeParentMethod(method, args);
                    }

                    boolean hasParameter(Method method) {
                        Parameter[] params = method.getParameters();
                        return (params.length >= 2 && params[1].getType().equals(Object.class));
                    }

                    Object addParameter(Object parameter) {
                        parameter = (parameter == null) ? new MapperMethod.ParamMap() : parameter;
                        if (parameter instanceof Map) {
                            return addParameter((Map) parameter);
                        } else {
                            throw new RuntimeException("Motif inattendu Dites-moi si vous venez ici ~:" + parameter.getClass().getName());
                        }
                    }

                    Object addParameter(Map map) {
                        //Paramètres implicites définis ici
                        map.put("insertValue", "foo");
                        map.put("updateValue", "bar");
                        return map;
                    }
                });
    }
}

Pour expliquer brièvement ce que vous faites ...

MyBatis a une chaîne dans le premier argument nommé insert / select / update / delete de la classe SqlSession. Analysez le contenu de Mapper et émettez SQL via une méthode qui a Object comme deuxième argument. Dans CustomSqlSessionConfig, étendez `` SQLSessionTemplate '' et ajoutez un objet qui semble inapproprié pour remplacer chaque méthode et passez-le via un argument au paramètre du deuxième argument (en fait Map) et exécutez-le. J'essaye de le faire.

Point de blocage: combiner le @Delegate de Lombok et le proxy dynamique

Ce code est auto-satisfaisant et peut vraiment être simplifié. Si vous essayez d'implémenter ce qui précède de manière simple, même si ce n'est pas un tel code, vous pouvez le réaliser en remplaçant la méthode cible comme indiqué dans ↓.

<détails>

Code stupide </ summary>

CustomSqlSessionConfig.java


(réduction)
@Override 
public int insert(String statement,Object parameter) {
    return super.insert(statement, addParameter(parameter));
}
(réduction)

Mais je n'aimais pas écrire la même chose encore et encore, alors j'ai essayé de m'y tenir.

Le proxy dynamique est une fonction implémentée à partir d'anciennes versions de Java qui crée dynamiquement un objet avec la méthode d'interface (groupe) spécifiée par l'argument et agrège le traitement dans le InvocationHandler spécifié. C'est une fonction qui peut être effectuée.

D'autre part, @Delegate de lombok est une annotation qui étend toutes les méthodes publiques des champs annotés en tant que méthodes de la classe qui contient le champ (vous pouvez l'ajuster). Il peut être difficile d'obtenir une image à première vue, mais si vous regardez ce site, il sera plus facile d'obtenir une image.

En combinant ces deux, vous pouvez traiter les méthodes arbitraires de votre propre classe (cette fois, il s'agit d'insérer ou de sélectionner) en une seule fois. Cela a tendance à être un petit code magique noir, mais si vous l'aimez, vous devriez l'aimer! Je ne sais pas

L'inconvénient de cette méthode est qu'il est difficile d'appeler la même méthode de la classe parent (appel de type super.xxx) depuis InvocationHandler. Puisque InvocationHandler prend un objet Method comme argument, ce serait bien de pouvoir faire method.invoke (...), mais lorsqu'il est combiné avec @Delegate, cela provoquera StackOverflow. C'est le même résultat si vous spécifiez la classe parente et obtenez l'objet Method avec la même signature de méthode. Il s'agit de la référence de l'API Method # invoke (https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/lang/reflect/Method.html#invoke (https://docs.oracle.com/en/java/javase/13/docs/api/java/javase/13/docs/api/java/javase/13/docs/api/java.htmlre/flava.html) En effet, l'appel de méthode est réalisé par invokeVirtual comme décrit dans java.lang.Object, java.lang.Object ...)).

If the underlying method is an instance method, it is invoked using dynamic method lookup as documented in The Java Language Specification, section 15.12.4.4; in particular, overriding based on the runtime type of the target object may occur. (Si la méthode sous-jacente est une méthode d'instance, elle sera appelée à l'aide de la référence de méthode dynamique décrite dans la section Spécification du langage Java et sera remplacée, en particulier, en fonction du type d'exécution de l'objet cible. )

Réaliser super.xxx par réflexion

MethodHandles.lookup().findSpecial(SqlSessionTemplate.class, method.getName(),
    MethodType.methodType(method.getReturnType(), 
    method.getParameterTypes()), SqlSessionTemplateExt.class);

Dans la classe enfant, vous devez obtenir la méthode de la classe parent `MethodHandle``` avec` `findSpecial. MethodHandle est différent de la réflexion Java conventionnelle, et le résultat dépend de la classe dans laquelle il est efficace. Si vous faites cela dans la classe d'implémentation InvocationHandler```, vous obtiendrez toujours une erreur.

Point suspect

  • Il est un peu suspect que ce soit une méthode pour ajouter un paramètre.
  • Ne fonctionne pas si le type d'objet paramètre est autre que Map. Cela ne fonctionnera pas si un cas autre que Map (confirmé qu'il peut être nul) arrive. Je n'ai pas pu rattraper autre chose que Map.

Version de bibliothèque confirmée

Nom de la bibliothèque version
Spring-Boot 2.3.4.RELEASE
mybatis-spring-boot-starter 2.1.3
lombok 5.1.0

À propos de l'exemple de code

Vous pouvez utiliser l'exemple de code comme vous le souhaitez, mais nous déclinons toute responsabilité quant au résultat.

Recommended Posts