Kaynağa Gözat

fixed github issues/241 支持 wrapper clone

= 7 yıl önce
ebeveyn
işleme
067a3b383b

+ 49 - 38
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/conditions/Wrapper.java

@@ -30,8 +30,9 @@ import com.baomidou.mybatisplus.core.metadata.Columns;
 import com.baomidou.mybatisplus.core.toolkit.ArrayUtils;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.core.toolkit.MapUtils;
-import com.baomidou.mybatisplus.core.toolkit.sql.SqlUtils;
+import com.baomidou.mybatisplus.core.toolkit.SerializationUtils;
 import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.core.toolkit.sql.SqlUtils;
 
 
 /**
@@ -42,19 +43,19 @@ import com.baomidou.mybatisplus.core.toolkit.StringUtils;
  * @author hubin , yanghu , Dyang , Caratacus
  * @Date 2016-11-7
  */
-@SuppressWarnings("serial" )
+@SuppressWarnings("serial")
 public abstract class Wrapper<T> implements Serializable {
 
     /**
      * 占位符
      */
-    private static final String PLACE_HOLDER = "{%s}" ;
+    private static final String PLACE_HOLDER = "{%s}";
 
-    private static final String MYBATIS_PLUS_TOKEN = "#{%s.paramNameValuePairs.%s}" ;
+    private static final String MYBATIS_PLUS_TOKEN = "#{%s.paramNameValuePairs.%s}";
 
-    private static final String MP_GENERAL_PARAMNAME = "MPGENVAL" ;
+    private static final String MP_GENERAL_PARAMNAME = "MPGENVAL";
 
-    private static final String DEFAULT_PARAM_ALIAS = "ew" ;
+    private static final String DEFAULT_PARAM_ALIAS = "ew";
     /**
      * 实现了TSQL语法的SQL实体
      */
@@ -73,7 +74,7 @@ public abstract class Wrapper<T> implements Serializable {
     /**
      * 拼接WHERE后应该是AND还是ORnull
      */
-    protected String AND_OR = "AND" ;
+    protected String AND_OR = "AND";
 
     /**
      * <p>
@@ -129,7 +130,7 @@ public abstract class Wrapper<T> implements Serializable {
         for (String column : columns) {
             if (StringUtils.isNotEmpty(column)) {
                 if (builder.length() > 0) {
-                    builder.append("," );
+                    builder.append(",");
                 }
                 builder.append(column);
             }
@@ -158,7 +159,7 @@ public abstract class Wrapper<T> implements Serializable {
                     }
                     builder.append(col).append(as);
                     if (i < column.length - 1) {
-                        builder.append("," );
+                        builder.append(",");
                     }
                 }
             }
@@ -192,13 +193,13 @@ public abstract class Wrapper<T> implements Serializable {
 
     @Override
     public String toString() {
-        StringBuilder sb = new StringBuilder("Wrapper<T>:" );
+        StringBuilder sb = new StringBuilder("Wrapper<T>:");
         String sqlSegment = getSqlSegment();
         sb.append(replacePlaceholder(sqlSegment));
         Object entity = getEntity();
         if (entity != null) {
-            sb.append("\n" );
-            sb.append("entity=" ).append(entity.toString());
+            sb.append("\n");
+            sb.append("entity=").append(entity.toString());
         }
         return sb.toString();
     }
@@ -215,7 +216,7 @@ public abstract class Wrapper<T> implements Serializable {
         if (StringUtils.isEmpty(sqlSegment)) {
             return StringUtils.EMPTY;
         }
-        return sqlSegment.replaceAll("#\\{" + getParamAlias() + ".paramNameValuePairs.MPGENVAL[0-9]+}" , "\\?" );
+        return sqlSegment.replaceAll("#\\{" + getParamAlias() + ".paramNameValuePairs.MPGENVAL[0-9]+}", "\\?");
     }
 
     /**
@@ -283,7 +284,7 @@ public abstract class Wrapper<T> implements Serializable {
      */
     public Wrapper<T> eq(boolean condition, String column, Object params) {
         if (condition) {
-            sql.WHERE(formatSql(String.format("%s = {0}" , column), params));
+            sql.WHERE(formatSql(String.format("%s = {0}", column), params));
         }
         return this;
     }
@@ -313,7 +314,7 @@ public abstract class Wrapper<T> implements Serializable {
      */
     public Wrapper<T> ne(boolean condition, String column, Object params) {
         if (condition) {
-            sql.WHERE(formatSql(String.format("%s <> {0}" , column), params));
+            sql.WHERE(formatSql(String.format("%s <> {0}", column), params));
         }
         return this;
     }
@@ -341,7 +342,7 @@ public abstract class Wrapper<T> implements Serializable {
      * @param params
      * @return
      */
-    @SuppressWarnings({"rawtypes" , "unchecked"})
+    @SuppressWarnings({"rawtypes", "unchecked"})
     public Wrapper<T> allEq(boolean condition, Map<String, Object> params) {
         if (condition && MapUtils.isNotEmpty(params)) {
             Iterator iterator = params.entrySet().iterator();
@@ -349,7 +350,7 @@ public abstract class Wrapper<T> implements Serializable {
                 Map.Entry<String, Object> entry = (Map.Entry<String, Object>) iterator.next();
                 Object value = entry.getValue();
                 if (StringUtils.checkValNotNull(value)) {
-                    sql.WHERE(formatSql(String.format("%s = {0}" , entry.getKey()), entry.getValue()));
+                    sql.WHERE(formatSql(String.format("%s = {0}", entry.getKey()), entry.getValue()));
                 }
 
             }
@@ -366,7 +367,7 @@ public abstract class Wrapper<T> implements Serializable {
      * @param params
      * @return
      */
-    @SuppressWarnings({"rawtypes" , "unchecked"})
+    @SuppressWarnings({"rawtypes", "unchecked"})
     public Wrapper<T> allEq(Map<String, Object> params) {
         return allEq(true, params);
     }
@@ -383,7 +384,7 @@ public abstract class Wrapper<T> implements Serializable {
      */
     public Wrapper<T> gt(boolean condition, String column, Object params) {
         if (condition) {
-            sql.WHERE(formatSql(String.format("%s > {0}" , column), params));
+            sql.WHERE(formatSql(String.format("%s > {0}", column), params));
         }
         return this;
     }
@@ -413,7 +414,7 @@ public abstract class Wrapper<T> implements Serializable {
      */
     public Wrapper<T> ge(boolean condition, String column, Object params) {
         if (condition) {
-            sql.WHERE(formatSql(String.format("%s >= {0}" , column), params));
+            sql.WHERE(formatSql(String.format("%s >= {0}", column), params));
         }
         return this;
     }
@@ -443,7 +444,7 @@ public abstract class Wrapper<T> implements Serializable {
      */
     public Wrapper<T> lt(boolean condition, String column, Object params) {
         if (condition) {
-            sql.WHERE(formatSql(String.format("%s < {0}" , column), params));
+            sql.WHERE(formatSql(String.format("%s < {0}", column), params));
         }
         return this;
     }
@@ -473,7 +474,7 @@ public abstract class Wrapper<T> implements Serializable {
      */
     public Wrapper<T> le(boolean condition, String column, Object params) {
         if (condition) {
-            sql.WHERE(formatSql(String.format("%s <= {0}" , column), params));
+            sql.WHERE(formatSql(String.format("%s <= {0}", column), params));
         }
         return this;
     }
@@ -613,7 +614,7 @@ public abstract class Wrapper<T> implements Serializable {
     public Wrapper<T> or(boolean condition, String sqlOr, Object... params) {
         if (condition) {
             if (StringUtils.isEmpty(sql.toString())) {
-                AND_OR = "OR" ;
+                AND_OR = "OR";
             }
             sql.OR().WHERE(formatSql(sqlOr, params));
         }
@@ -666,7 +667,7 @@ public abstract class Wrapper<T> implements Serializable {
     public Wrapper<T> orNew(boolean condition, String sqlOr, Object... params) {
         if (condition) {
             if (StringUtils.isEmpty(sql.toString())) {
-                AND_OR = "OR" ;
+                AND_OR = "OR";
             }
             sql.OR_NEW().WHERE(formatSql(sqlOr, params));
         }
@@ -808,7 +809,7 @@ public abstract class Wrapper<T> implements Serializable {
      */
     public Wrapper<T> orderBy(boolean condition, String columns, boolean isAsc) {
         if (condition && StringUtils.isNotEmpty(columns)) {
-            sql.ORDER_BY(columns + (isAsc ? " ASC" : " DESC" ));
+            sql.ORDER_BY(columns + (isAsc ? " ASC" : " DESC"));
         }
         return this;
     }
@@ -943,9 +944,9 @@ public abstract class Wrapper<T> implements Serializable {
             StringBuilder inSql = new StringBuilder();
             inSql.append(column);
             if (isNot) {
-                inSql.append(" NOT" );
+                inSql.append(" NOT");
             }
-            inSql.append(" LIKE {0}" );
+            inSql.append(" LIKE {0}");
             sql.WHERE(formatSql(inSql.toString(), SqlUtils.concatLike(value, type)));
         }
     }
@@ -1138,7 +1139,7 @@ public abstract class Wrapper<T> implements Serializable {
      */
     public Wrapper<T> in(boolean condition, String column, String value) {
         if (condition && StringUtils.isNotEmpty(value)) {
-            in(column, StringUtils.splitWorker(value, "," , -1, false));
+            in(column, StringUtils.splitWorker(value, ",", -1, false));
         }
         return this;
     }
@@ -1168,7 +1169,7 @@ public abstract class Wrapper<T> implements Serializable {
      */
     public Wrapper<T> notIn(boolean condition, String column, String value) {
         if (condition && StringUtils.isNotEmpty(value)) {
-            notIn(column, StringUtils.splitWorker(value, "," , -1, false));
+            notIn(column, StringUtils.splitWorker(value, ",", -1, false));
         }
         return this;
     }
@@ -1320,18 +1321,18 @@ public abstract class Wrapper<T> implements Serializable {
             StringBuilder inSql = new StringBuilder();
             inSql.append(column);
             if (isNot) {
-                inSql.append(" NOT" );
+                inSql.append(" NOT");
             }
-            inSql.append(" IN " );
-            inSql.append("(" );
+            inSql.append(" IN ");
+            inSql.append("(");
             int size = value.size();
             for (int i = 0; i < size; i++) {
                 inSql.append(String.format(PLACE_HOLDER, i));
                 if (i + 1 < size) {
-                    inSql.append("," );
+                    inSql.append(",");
                 }
             }
-            inSql.append(")" );
+            inSql.append(")");
             return inSql.toString();
         }
         return null;
@@ -1350,7 +1351,7 @@ public abstract class Wrapper<T> implements Serializable {
      */
     public Wrapper<T> between(boolean condition, String column, Object val1, Object val2) {
         if (condition) {
-            sql.WHERE(formatSql(String.format("%s BETWEEN {0} AND {1}" , column), val1, val2));
+            sql.WHERE(formatSql(String.format("%s BETWEEN {0} AND {1}", column), val1, val2));
         }
         return this;
     }
@@ -1382,7 +1383,7 @@ public abstract class Wrapper<T> implements Serializable {
      */
     public Wrapper<T> notBetween(boolean condition, String column, Object val1, Object val2) {
         if (condition) {
-            sql.WHERE(formatSql(String.format("%s NOT BETWEEN {0} AND {1}" , column), val1, val2));
+            sql.WHERE(formatSql(String.format("%s NOT BETWEEN {0} AND {1}", column), val1, val2));
         }
         return this;
     }
@@ -1532,10 +1533,10 @@ public abstract class Wrapper<T> implements Serializable {
      */
     public Wrapper<T> setParamAlias(String paramAlias) {
         if (StringUtils.isNotEmpty(getSqlSegment())) {
-            throw new MybatisPlusException("Error: Please call this method when initializing!" );
+            throw new MybatisPlusException("Error: Please call this method when initializing!");
         }
         if (StringUtils.isNotEmpty(this.paramAlias)) {
-            throw new MybatisPlusException("Error: Please do not call the method repeatedly!" );
+            throw new MybatisPlusException("Error: Please do not call the method repeatedly!");
         }
         this.paramAlias = paramAlias;
         return this;
@@ -1559,4 +1560,14 @@ public abstract class Wrapper<T> implements Serializable {
             }
         };
     }
+
+    /**
+     * <p>
+     * 克隆本身 fixed github issues/241
+     * </p>
+     */
+    public Wrapper<T> clone() {
+        return SerializationUtils.clone(this);
+    }
 }
+

+ 289 - 0
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/SerializationUtils.java

@@ -0,0 +1,289 @@
+package com.baomidou.mybatisplus.core.toolkit;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamClass;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
+
+
+/**
+ * <p>Assists with the serialization process and performs additional functionality based
+ * on serialization.</p>
+ * <p>
+ * <ul>
+ * <li>Deep clone using serialization
+ * <li>Serialize managing finally and IOException
+ * <li>Deserialize managing finally and IOException
+ * </ul>
+ * <p>
+ * <p>This class throws exceptions for invalid {@code null} inputs.
+ * Each method documents its behaviour in more detail.</p>
+ * <p>
+ * <p>#ThreadSafe#</p>
+ * <p>copy from org.apache.commons.lang3.SerializationUtils</p>
+ *
+ * @since 1.0
+ */
+public class SerializationUtils {
+
+    /**
+     * <p>SerializationUtils instances should NOT be constructed in standard programming.
+     * Instead, the class should be used as {@code SerializationUtils.clone(object)}.</p>
+     * <p>
+     * <p>This constructor is public to permit tools that require a JavaBean instance
+     * to operate.</p>
+     *
+     * @since 2.0
+     */
+    public SerializationUtils() {
+        super();
+    }
+
+    // Clone
+    //-----------------------------------------------------------------------
+
+    /**
+     * <p>Deep clone an {@code Object} using serialization.</p>
+     * <p>
+     * <p>This is many times slower than writing clone methods by hand
+     * on all objects in your object graph. However, for complex object
+     * graphs, or for those that don't support deep cloning this can
+     * be a simple alternative implementation. Of course all the objects
+     * must be {@code Serializable}.</p>
+     *
+     * @param <T>    the type of the object involved
+     * @param object the {@code Serializable} object to clone
+     * @return the cloned object
+     * @throws MybatisPlusException (runtime) if the serialization fails
+     */
+    public static <T extends Serializable> T clone(final T object) {
+        if (object == null) {
+            return null;
+        }
+        final byte[] objectData = serialize(object);
+        final ByteArrayInputStream bais = new ByteArrayInputStream(objectData);
+
+        try (ClassLoaderAwareObjectInputStream in = new ClassLoaderAwareObjectInputStream(bais,
+            object.getClass().getClassLoader())) {
+            /*
+             * when we serialize and deserialize an object,
+             * it is reasonable to assume the deserialized object
+             * is of the same type as the original serialized object
+             */
+            @SuppressWarnings("unchecked") // see above
+            final T readObject = (T) in.readObject();
+            return readObject;
+
+        } catch (final ClassNotFoundException ex) {
+            throw new MybatisPlusException("ClassNotFoundException while reading cloned object data", ex);
+        } catch (final IOException ex) {
+            throw new MybatisPlusException("IOException while reading or closing cloned object data", ex);
+        }
+    }
+
+    /**
+     * Performs a serialization roundtrip. Serializes and deserializes the given object, great for testing objects that
+     * implement {@link Serializable}.
+     *
+     * @param <T> the type of the object involved
+     * @param msg the object to roundtrip
+     * @return the serialized and deserialized object
+     * @since 3.3
+     */
+    @SuppressWarnings("unchecked") // OK, because we serialized a type `T`
+    public static <T extends Serializable> T roundtrip(final T msg) {
+        return (T) SerializationUtils.deserialize(SerializationUtils.serialize(msg));
+    }
+
+    // Serialize
+    //-----------------------------------------------------------------------
+
+    /**
+     * <p>Serializes an {@code Object} to the specified stream.</p>
+     * <p>
+     * <p>The stream will be closed once the object is written.
+     * This avoids the need for a finally clause, and maybe also exception
+     * handling, in the application code.</p>
+     * <p>
+     * <p>The stream passed in is not buffered internally within this method.
+     * This is the responsibility of your application if desired.</p>
+     *
+     * @param obj          the object to serialize to bytes, may be null
+     * @param outputStream the stream to write to, must not be null
+     * @throws IllegalArgumentException if {@code outputStream} is {@code null}
+     * @throws MybatisPlusException     (runtime) if the serialization fails
+     */
+    public static void serialize(final Serializable obj, final OutputStream outputStream) {
+        isTrue(outputStream != null, "The OutputStream must not be null");
+        try (ObjectOutputStream out = new ObjectOutputStream(outputStream)) {
+            out.writeObject(obj);
+        } catch (final IOException ex) {
+            throw new MybatisPlusException(ex);
+        }
+    }
+
+    /**
+     * <p>Serializes an {@code Object} to a byte array for
+     * storage/serialization.</p>
+     *
+     * @param obj the object to serialize to bytes
+     * @return a byte[] with the converted Serializable
+     * @throws MybatisPlusException (runtime) if the serialization fails
+     */
+    public static byte[] serialize(final Serializable obj) {
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
+        serialize(obj, baos);
+        return baos.toByteArray();
+    }
+
+    // Deserialize
+    //-----------------------------------------------------------------------
+
+    /**
+     * <p>
+     * Deserializes an {@code Object} from the specified stream.
+     * </p>
+     * <p>
+     * <p>
+     * The stream will be closed once the object is written. This avoids the need for a finally clause, and maybe also
+     * exception handling, in the application code.
+     * </p>
+     * <p>
+     * <p>
+     * The stream passed in is not buffered internally within this method. This is the responsibility of your
+     * application if desired.
+     * </p>
+     * <p>
+     * <p>
+     * If the call site incorrectly types the return value, a {@link ClassCastException} is thrown from the call site.
+     * Without Generics in this declaration, the call site must type cast and can cause the same ClassCastException.
+     * Note that in both cases, the ClassCastException is in the call site, not in this method.
+     * </p>
+     *
+     * @param <T>         the object type to be deserialized
+     * @param inputStream the serialized object input stream, must not be null
+     * @return the deserialized object
+     * @throws IllegalArgumentException if {@code inputStream} is {@code null}
+     * @throws MybatisPlusException     (runtime) if the serialization fails
+     */
+    public static <T> T deserialize(final InputStream inputStream) {
+        isTrue(inputStream != null, "The InputStream must not be null");
+        try (ObjectInputStream in = new ObjectInputStream(inputStream)) {
+            @SuppressWarnings("unchecked") final T obj = (T) in.readObject();
+            return obj;
+        } catch (final ClassNotFoundException | IOException ex) {
+            throw new MybatisPlusException(ex);
+        }
+    }
+
+    /**
+     * <p>
+     * Deserializes a single {@code Object} from an array of bytes.
+     * </p>
+     * <p>
+     * <p>
+     * If the call site incorrectly types the return value, a {@link ClassCastException} is thrown from the call site.
+     * Without Generics in this declaration, the call site must type cast and can cause the same ClassCastException.
+     * Note that in both cases, the ClassCastException is in the call site, not in this method.
+     * </p>
+     *
+     * @param <T>        the object type to be deserialized
+     * @param objectData the serialized object, must not be null
+     * @return the deserialized object
+     * @throws IllegalArgumentException if {@code objectData} is {@code null}
+     * @throws MybatisPlusException     (runtime) if the serialization fails
+     */
+    public static <T> T deserialize(final byte[] objectData) {
+        isTrue(objectData != null, "The byte[] must not be null");
+        return SerializationUtils.deserialize(new ByteArrayInputStream(objectData));
+    }
+
+    /**
+     * <p>Custom specialization of the standard JDK {@link ObjectInputStream}
+     * that uses a custom  <code>ClassLoader</code> to resolve a class.
+     * If the specified <code>ClassLoader</code> is not able to resolve the class,
+     * the context classloader of the current thread will be used.
+     * This way, the standard deserialization work also in web-application
+     * containers and application servers, no matter in which of the
+     * <code>ClassLoader</code> the particular class that encapsulates
+     * serialization/deserialization lives. </p>
+     * <p>
+     * <p>For more in-depth information about the problem for which this
+     * class here is a workaround, see the JIRA issue LANG-626. </p>
+     */
+    static class ClassLoaderAwareObjectInputStream extends ObjectInputStream {
+
+        private static final Map<String, Class<?>> primitiveTypes =
+            new HashMap<>();
+
+        static {
+            primitiveTypes.put("byte", byte.class);
+            primitiveTypes.put("short", short.class);
+            primitiveTypes.put("int", int.class);
+            primitiveTypes.put("long", long.class);
+            primitiveTypes.put("float", float.class);
+            primitiveTypes.put("double", double.class);
+            primitiveTypes.put("boolean", boolean.class);
+            primitiveTypes.put("char", char.class);
+            primitiveTypes.put("void", void.class);
+        }
+
+        private final ClassLoader classLoader;
+
+        /**
+         * Constructor.
+         *
+         * @param in          The <code>InputStream</code>.
+         * @param classLoader classloader to use
+         * @throws IOException if an I/O error occurs while reading stream header.
+         * @see ObjectInputStream
+         */
+        ClassLoaderAwareObjectInputStream(final InputStream in, final ClassLoader classLoader) throws IOException {
+            super(in);
+            this.classLoader = classLoader;
+        }
+
+        /**
+         * Overridden version that uses the parameterized <code>ClassLoader</code> or the <code>ClassLoader</code>
+         * of the current <code>Thread</code> to resolve the class.
+         *
+         * @param desc An instance of class <code>ObjectStreamClass</code>.
+         * @return A <code>Class</code> object corresponding to <code>desc</code>.
+         * @throws IOException            Any of the usual Input/Output exceptions.
+         * @throws ClassNotFoundException If class of a serialized object cannot be found.
+         */
+        @Override
+        protected Class<?> resolveClass(final ObjectStreamClass desc) throws IOException, ClassNotFoundException {
+            final String name = desc.getName();
+            try {
+                return Class.forName(name, false, classLoader);
+            } catch (final ClassNotFoundException ex) {
+                try {
+                    return Class.forName(name, false, Thread.currentThread().getContextClassLoader());
+                } catch (final ClassNotFoundException cnfe) {
+                    final Class<?> cls = primitiveTypes.get(name);
+                    if (cls != null) {
+                        return cls;
+                    }
+                    throw cnfe;
+                }
+            }
+        }
+
+    }
+
+    public static void isTrue(final boolean expression, final String message, final Object... values) {
+        if (!expression) {
+            throw new IllegalArgumentException(String.format(message, values));
+        }
+    }
+}

+ 8 - 3
mybatis-plus-extension/src/test/java/com/baomidou/mybatisplus/extension/test/EntityWrapperTest.java

@@ -62,10 +62,15 @@ public class EntityWrapperTest {
          * 实体带where ifneed
          */
         ew.setEntity(new User(1));
-        ew.where("name={0}" , "'123'" ).addFilterIfNeed(false, "id=12" );
-        String sqlSegment = ew.originalSql();
+        ew.where("name={0}", "'123'");
+        // 测试克隆
+        String sqlSegment = ew.clone().eq("a", 1).originalSql();
+        Assert.assertEquals("AND (name=? AND a = ?)", sqlSegment);
+        System.err.println("test11-clone = " + sqlSegment);
+        ew.addFilterIfNeed(false, "id=12");
+        sqlSegment = ew.originalSql();
         System.err.println("test11 = " + sqlSegment);
-        Assert.assertEquals("AND (name=?)" , sqlSegment);
+        Assert.assertEquals("AND (name=?)", sqlSegment);
     }
 
     @Test