[JAVA] So übergeben Sie ein Objekt in MyBatis an Mapper, ohne ein Argument durchzugehen

Ich konnte es zum Zeitpunkt der Veröffentlichung des Artikels nicht finden, also habe ich es nachgeschlagen.

Was ich machen wollte

Für diejenigen, die nur eine Schlussfolgerung wollen

Beispiel finden Sie hier. Ich möchte die addParameter-Methode in Zeile 66 von diesem Quellcode neu schreiben. Wenn Sie ein Objekt angeben, können Sie auf dieselbe Weise darauf zugreifen, wie Sie es als Argument übergeben haben.

Codebeschreibung

Gesamtbild

Zunächst wird der entsprechende Quellcode unten angezeigt.

Quellcode

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("Unerwartetes Muster Sag mir, ob du hierher kommst ~:" + parameter.getClass().getName());
                        }
                    }

                    Object addParameter(Map map) {
                        //Hier festgelegte implizite Parameter
                        map.put("insertValue", "foo");
                        map.put("updateValue", "bar");
                        return map;
                    }
                });
    }
}

Um kurz zu erklären, was Sie tun ...

MyBatis hat eine Zeichenfolge im ersten Argument mit dem Namen insert / select / update / delete der Klasse SqlSession. Analysieren Sie den Inhalt von Mapper und geben Sie SQL über eine Methode aus, deren zweites Argument Object ist. Erweitern Sie in CustomSqlSessionConfig `` `SQLSessionTemplate``` und fügen Sie ein Objekt hinzu, das unangemessen erscheint, um jede Methode zu überschreiben, und übergeben Sie es über ein Argument an den Parameter des zweiten Arguments (tatsächlich Map) und führen Sie es aus. Ich versuche es zu tun.

Knackpunkt: Kombination von @Delegate und dynamischem Proxy von lombok

Dieser Code ist selbstzufrieden und kann wirklich einfacher gemacht werden. Wenn Sie versuchen, das oben Genannte auf einfache Weise zu implementieren, auch wenn es sich nicht um einen solchen Code handelt, können Sie dies realisieren, indem Sie die Zielmethode wie in ↓ gezeigt überschreiben.

Blöder Code

CustomSqlSessionConfig.java


(Kürzung)
@Override 
public int insert(String statement,Object parameter) {
    return super.insert(statement, addParameter(parameter));
}
(Kürzung)

Aber ich wollte nicht immer und immer wieder dasselbe schreiben, also habe ich versucht, mich daran zu halten.

Dynamischer Proxy ist eine aus älteren Java-Versionen implementierte Funktion, die dynamisch ein Objekt mit der durch das Argument angegebenen Schnittstellenmethode (Gruppenmethode) erstellt und die Verarbeitung im angegebenen InvocationHandler aggregiert. Es ist eine Funktion, die ausgeführt werden kann.

Auf der anderen Seite ist @Delegate von lombok eine Annotation, die alle öffentlichen Methoden von annotierten Feldern als Methoden der Klasse erweitert, die das Feld enthält (Sie können es optimieren). Es mag auf den ersten Blick schwierig sein, ein Bild zu erhalten, aber wenn Sie sich diese Site ansehen, ist es einfacher, ein Bild zu erhalten.

Durch Kombinieren dieser beiden können Sie beliebige Methoden Ihrer eigenen Klasse (diesmal Einfügen oder Auswählen) gleichzeitig verarbeiten. Es ist in der Regel ein kleiner schwarzer magischer Code, aber wenn Sie ihn mögen, sollten Sie ihn lieben! Ich weiß nicht w

Der Nachteil dieser Methode ist, dass es schwierig ist, dieselbe Methode der übergeordneten Klasse (super.xxx-ähnlicher Aufruf) von InvocationHandler aufzurufen. Da InvocationHandler ein Methodenobjekt als Argument verwendet, wäre es schön, `method.invoke (...)` ausführen zu können, aber wenn es mit @Delegate kombiniert wird, verursacht es StackOverflow. Dies ist das gleiche Ergebnis, wenn Sie die übergeordnete Klasse angeben und das Methodenobjekt mit derselben Methodensignatur abrufen. Dies ist die API-Referenz zum Aufrufen der Methode # (https://docs.oracle.com/de/java/javase/13/docs/api/java.base/java/lang/reflect/Method.html#invoke (https://docs.oracle.com/en/java/javase/13/docs/ap//). Dies liegt daran, dass der Methodenaufruf von invokeVirtual wie in java.lang.Object, java.lang.Object ...) beschrieben wird.

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. (Wenn es sich bei der zugrunde liegenden Methode um eine Instanzmethode handelt, wird sie unter Verwendung der im Abschnitt Java Language Specification beschriebenen Referenz für dynamische Methoden aufgerufen und insbesondere basierend auf dem Laufzeittyp des Zielobjekts überschrieben. )

Super.xxx durch Reflexion realisieren

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

Innerhalb der untergeordneten Klasse müssen Sie die Methode `MethodHandle``` der übergeordneten Klasse mit` findSpecial``` abrufen. MethodHandle unterscheidet sich von der herkömmlichen Java-Reflektion und das Ergebnis hängt davon ab, in welcher Klasse es wirksam ist. Selbst wenn Sie dies in der Implementierungsklasse "InvocationHandler" tun, wird immer noch eine Fehlermeldung angezeigt.

Verdächtiger Punkt

  • Es ist etwas verdächtig, ob es sich um eine Methode zum Hinzufügen eines Parameters handelt.
  • Funktioniert nicht, wenn der Parameterobjekttyp nicht Map ist. Es funktioniert nicht, wenn ein anderer Fall als Map (bestätigt, dass er möglicherweise null ist) auftritt. Ich konnte nichts anderes als Map einholen.

Bestätigte Bibliotheksversion

Bibliotheksname Ausführung
Spring-Boot 2.3.4.RELEASE
mybatis-spring-boot-starter 2.1.3
lombok 5.1.0

Informationen zum Beispielcode

Sie können den Beispielcode beliebig verwenden, wir übernehmen jedoch keine Verantwortung für das Ergebnis.

Recommended Posts