diff --git a/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/FastmybatisContext.java b/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/FastmybatisContext.java index 268c4efa8e6d8fb9ec44737c45f4d0ba649a6a9e..aca7657ab31798ee3862613813fe8d12ba003fa5 100644 --- a/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/FastmybatisContext.java +++ b/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/FastmybatisContext.java @@ -7,16 +7,17 @@ import com.gitee.fastmybatis.core.ext.spi.MapperBuilder; import com.gitee.fastmybatis.core.ext.spi.SpiContext; import com.gitee.fastmybatis.core.mapper.CrudMapper; import com.gitee.fastmybatis.core.mapper.Mapper; -import com.gitee.fastmybatis.core.mapper.SchMapper; +import com.gitee.fastmybatis.core.update.ModifyAttrsRecordProxyFactory; +import com.gitee.fastmybatis.core.update.Reflectors; +import com.gitee.fastmybatis.core.update.UpdateWrapper; import com.gitee.fastmybatis.core.util.ClassUtil; +import com.gitee.fastmybatis.core.util.ConvertUtil; import org.apache.ibatis.logging.Log; import org.apache.ibatis.logging.LogFactory; +import org.apache.ibatis.reflection.Reflector; import java.lang.reflect.Field; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** * @author thc @@ -33,6 +34,7 @@ public class FastmybatisContext { /** * 设置逻辑删除时额外需要更新的字段 + * * @param equalColumns 待更新字段 * @see com.gitee.fastmybatis.core.mapper.DeleteMapper#deleteByIds(Collection, EqualColumn...) deleteByIds * @see com.gitee.fastmybatis.core.mapper.DeleteMapper#deleteByColumn(String, Object, EqualColumn...) deleteByColumn @@ -65,6 +67,7 @@ public class FastmybatisContext { /** * 获取实体类对应的数据库主键名称 + * * @param entityClass 实体类class * @return 返回数据库主键名称 */ @@ -78,6 +81,7 @@ public class FastmybatisContext { /** * 获取实体类对应的数据库主键名称 + * * @param mapperClass mapperClass * @return 返回数据库主键名称 */ @@ -88,6 +92,7 @@ public class FastmybatisContext { /** * 获取实体类主键对应的JAVA字段名称 + * * @param entityClass 实体类class * @return 返回主键java字段名称 */ @@ -109,6 +114,12 @@ public class FastmybatisContext { if (entity == null) { return null; } + + + if (entity instanceof UpdateWrapper) { + return getPkValueByGetter(entity); + } + Class entityClass = entity.getClass(); String pkJavaName = getPkJavaName(entityClass); Field field = ClassUtil.findField(entityClass, pkJavaName); @@ -124,4 +135,68 @@ public class FastmybatisContext { } } + private static Object getPkValueByGetter(Object entity) { + Class entityClass = entity.getClass(); + if (entity instanceof UpdateWrapper) { + entityClass = ModifyAttrsRecordProxyFactory.getSrcClass(entity.getClass()); + } + + String pkJavaName = getPkJavaName(entityClass); + Field field = ClassUtil.findField(entityClass, pkJavaName); + if (field == null) { + return null; + } + + + Reflector reflector = Reflectors.of(entityClass); + try { + return reflector.getGetInvoker(field.getName()).invoke(entity, null); + } catch (Exception ignored) { + // do nothing here. + } + + return null; + } + + public static void setPkValue(T entity, Object id) { + setPkValue(entity, entity.getClass(), id); + } + + public static void setPkValue(Object entity, Class entityClass, Object id) { + + String pkJavaName = getPkJavaName(entityClass); + Field field = ClassUtil.findField(entityClass, pkJavaName); + if (field == null) { + return; + } + + Reflector reflector = Reflectors.of(entityClass); + try { + reflector.getSetInvoker(field.getName()).invoke(entity, new Object[]{ConvertUtil.convert(id, field.getType())}); + } catch (Exception ignored) { + // do nothing here. + } + + + } + + public static Set getIgnoreProperties(Class srcClass, Set includeProperties) { + if (null == includeProperties) { + includeProperties = Collections.emptySet(); + } + + + Set IgnoreProperties = new HashSet<>(); + + Field[] fields = srcClass.getDeclaredFields(); + for (Field field : fields) { + if (!includeProperties.contains(field.getName())) { + IgnoreProperties.add(field.getName()); + } + } + + + return IgnoreProperties; + + } } diff --git a/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/mapper/UpdateMapper.java b/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/mapper/UpdateMapper.java index dc98df3f9259994c737cdf1042a12604ddbd8397..4bbd496fdd4d6fc476c707934c4bf44991f1259d 100644 --- a/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/mapper/UpdateMapper.java +++ b/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/mapper/UpdateMapper.java @@ -1,11 +1,17 @@ package com.gitee.fastmybatis.core.mapper; +import com.gitee.fastmybatis.core.FastmybatisContext; import com.gitee.fastmybatis.core.query.Query; +import com.gitee.fastmybatis.core.update.ModifyAttrsRecordProxyFactory; +import com.gitee.fastmybatis.core.update.UpdateWrapper; import org.apache.ibatis.annotations.Param; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Objects; + /** * 具备更新功能的Mapper @@ -32,17 +38,18 @@ public interface UpdateMapper extends Mapper { /** * 根据条件更新
- *
-     *{@literal
+     * 
+     * {@literal
      * Query query = new Query().eq("state", 2);
      * TUser user = new TUser();
      * user.setUsername("李四");
      * int i = mapper.updateByQuery(user, query);
      * }
      * 对应SQL: UPDATE `t_user` SET `username`=? WHERE state = ?
-     *
- * @param entity 待更新的数据 - * @param query 更新条件 + *
+ * + * @param entity 待更新的数据 + * @param query 更新条件 * @param ignoreProperties 指定忽略更新的字段,可以是实体类属性名,也可以是数据库字段名 * @return 受影响行数 */ @@ -53,8 +60,8 @@ public interface UpdateMapper extends Mapper { /** * 根据条件更新
- *
-     *{@literal
+     * 
+     * {@literal
      * Query query = new Query().eq("state", 2);
      * TUser user = new TUser();
      * user.setUsername("李四");
@@ -63,18 +70,79 @@ public interface UpdateMapper extends Mapper {
      * int i = mapper.updateByQuery(user, query, Arrays.asList("addTime"));
      * }
      * 对应SQL: UPDATE `t_user` SET `username`=? WHERE state = ?
-     *
- * @param entity 待更新的数据 - * @param query 更新条件 + *
+ * + * @param entity 待更新的数据 + * @param query 更新条件 * @param ignoreProperties 指定忽略更新的字段,可以是实体类属性名,也可以是数据库字段名 * @return 受影响行数 */ int updateByQuery(@Param("entity") E entity, @Param("query") Query query, @Param("ignoreProperties") List ignoreProperties); + + /** + * 根据条件强制更新,只要是调用setter赋值的属性都更新哪怕赋的值为null
+ *
+     * {@literal
+     * TUser user = UpdateEntity.of(TUser.class,123L);
+     * user.setUsername(null);
+     * user.setAddTime(new Date());
+     * // 强制更新 user_name和add_time
+     * int i = mapper.forceUpdateById(user);
+     * }
+     * 对应SQL: UPDATE `t_user` SET `username`=? WHERE state = ?
+     * 
+ * + * @param entity 待更新的数据 + * @return 受影响行数 + */ + default int forceUpdateById(E entity) { + return forceUpdateById(entity, true); + } + + /** + * 根据条件强制更新,只要是调用setter赋值的属性都更新哪怕赋的值为null
+ *
+     * {@literal
+     * TUser user = UpdateEntity.of(TUser.class,123L);
+     * user.setUsername(null);
+     * user.setAddTime(new Date());
+     * // 强制更新 user_name和add_time
+     * int i = mapper.forceUpdateById(user,true);
+     * }
+     * 对应SQL: UPDATE `t_user` SET `username`=? WHERE state = ?
+     * 
+ * + * @param entity 待更新的数据 + * @param ignoreLogicDeleteColumn 无视逻辑删除字段 + * @return 受影响行数 + */ + default int forceUpdateById(E entity, boolean ignoreLogicDeleteColumn) { + Objects.requireNonNull(entity, "entity can not null"); + if (!(entity instanceof UpdateWrapper)) { + throw new IllegalStateException("please wrapper entity with UpdateEntity.of() before call setter!!"); + } + + UpdateWrapper uw = (UpdateWrapper) entity; + String pkColumnName = FastmybatisContext.getPkColumnName(ModifyAttrsRecordProxyFactory.getSrcClass(entity.getClass())); + Object pkValue = FastmybatisContext.getPkValue(entity); + Query query = new Query().eq(pkColumnName, pkValue).enableForceUpdate(); + if (ignoreLogicDeleteColumn) { + query.ignoreLogicDeleteColumn(); + } + + + List ignoreProperties = new ArrayList<>(); + ignoreProperties.addAll(FastmybatisContext.getIgnoreProperties(ModifyAttrsRecordProxyFactory.getSrcClass(entity.getClass()), uw.getUpdates().keySet())); + + return updateByQuery(entity, query, ignoreProperties); + } + + /** * 根据条件更新,map中的数据转化成update语句set部分,key为数据库字段名
- *
-     *{@literal
+     * 
+     * {@literal
      * Query query = new Query().eq("id", 1);
      * // key为数据库字段名
      * Map map = new LinkedHashMap<>();
diff --git a/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/update/ModifyAttrsRecordHandler.java b/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/update/ModifyAttrsRecordHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..d26f1fc1c0d763939fd2292d694444826630c84d
--- /dev/null
+++ b/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/update/ModifyAttrsRecordHandler.java
@@ -0,0 +1,40 @@
+package com.gitee.fastmybatis.core.update;
+
+
+import com.gitee.fastmybatis.core.util.StringUtil;
+import org.apache.ibatis.javassist.util.proxy.MethodHandler;
+
+import java.lang.reflect.Method;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+class ModifyAttrsRecordHandler implements MethodHandler {
+
+    //更新内容
+    private final Map updates = new LinkedHashMap<>();
+
+    public Map getUpdates() {
+        return updates;
+    }
+
+
+    @Override
+    public Object invoke(Object self, Method originalMethod, Method proxyMethod, Object[] args) throws Throwable {
+
+
+        String methodName = originalMethod.getName();
+        if (methodName.startsWith("set")
+                && methodName.length() > 3
+                && Character.isUpperCase(methodName.charAt(3))
+                && originalMethod.getParameterCount() == 1) {
+            String property = StringUtil.firstCharToLowerCase(originalMethod.getName().substring(3));
+            updates.put(property, args[0]);
+        }
+
+        return proxyMethod.invoke(self, args);
+    }
+
+
+}
+
+
diff --git a/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/update/ModifyAttrsRecordProxyFactory.java b/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/update/ModifyAttrsRecordProxyFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..55e03cbbbb0aa04247c895c336d25d7beebbc151
--- /dev/null
+++ b/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/update/ModifyAttrsRecordProxyFactory.java
@@ -0,0 +1,89 @@
+package com.gitee.fastmybatis.core.update;
+
+import org.apache.ibatis.javassist.util.proxy.ProxyFactory;
+import org.apache.ibatis.javassist.util.proxy.ProxyObject;
+import org.apache.ibatis.logging.LogFactory;
+
+import java.util.Arrays;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+public class ModifyAttrsRecordProxyFactory {
+
+    private static final ModifyAttrsRecordProxyFactory instance = new ModifyAttrsRecordProxyFactory();
+
+    public static ModifyAttrsRecordProxyFactory getInstance() {
+        return instance;
+    }
+
+    private ModifyAttrsRecordProxyFactory() {
+    }
+
+
+    /**
+     * srcClass -> proxyClass
+     */
+    private static ConcurrentHashMap, Class> srcProxyMap = new ConcurrentHashMap<>();
+
+    /**
+     * proxyClass-> srcClass
+     */
+    private static ConcurrentHashMap, Class> proxySrcMap = new ConcurrentHashMap<>();
+
+    public static Class getProxyClass(Class src) {
+        return srcProxyMap.get(src);
+    }
+
+    public static Class getSrcClass(Class proxy) {
+        return proxySrcMap.get(proxy);
+    }
+
+    private Class createProxyClass(Class target) {
+        Class proxyClass = srcProxyMap.get(target);
+        if (null == proxyClass) {
+            synchronized (ModifyAttrsRecordProxyFactory.class) {
+                proxyClass = srcProxyMap.get(target);
+                if (null == proxyClass) {
+                    ProxyFactory factory = new ProxyFactory();
+                    factory.setSuperclass(target);
+
+                    Class[] interfaces = Arrays.copyOf(target.getInterfaces(), target.getInterfaces().length + 1);
+                    interfaces[interfaces.length - 1] = UpdateWrapper.class;
+                    factory.setInterfaces(interfaces);
+
+                    proxyClass = factory.createClass();
+                    srcProxyMap.put(target, proxyClass);
+                    proxySrcMap.put(proxyClass, target);
+
+                    return proxyClass;
+                }
+            }
+        }
+
+
+        return proxyClass;
+
+
+    }
+
+
+    public synchronized  T get(Class target) {
+
+        Class proxyClass = createProxyClass(target);
+
+        T proxyObject = null;
+        try {
+            proxyObject = (T) proxyClass.newInstance();
+            ((ProxyObject) proxyObject).setHandler(new ModifyAttrsRecordHandler());
+        } catch (Throwable e) {
+            LogFactory.getLog(ModifyAttrsRecordProxyFactory.class).error(e.toString(), e);
+        }
+
+        return proxyObject;
+    }
+
+
+}
+
+
+
diff --git a/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/update/Reflectors.java b/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/update/Reflectors.java
new file mode 100644
index 0000000000000000000000000000000000000000..273a3dfd164c20f257fd56b97d0414ad286e3701
--- /dev/null
+++ b/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/update/Reflectors.java
@@ -0,0 +1,17 @@
+
+package com.gitee.fastmybatis.core.update;
+
+import org.apache.ibatis.reflection.Reflector;
+import org.apache.ibatis.util.MapUtil;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+public class Reflectors {
+
+    private static final  ConcurrentMap, Reflector> reflectorMap = new ConcurrentHashMap<>();
+
+    public static Reflector of(Class type){
+        return  MapUtil.computeIfAbsent(reflectorMap, type, Reflector::new);
+    }
+}
diff --git a/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/update/UpdateWrapper.java b/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/update/UpdateWrapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..9b372b8ca87e35f30d557fac488b551af5dc5769
--- /dev/null
+++ b/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/update/UpdateWrapper.java
@@ -0,0 +1,16 @@
+package com.gitee.fastmybatis.core.update;
+
+import org.apache.ibatis.javassist.util.proxy.ProxyObject;
+
+import java.io.Serializable;
+import java.util.Map;
+
+public interface UpdateWrapper extends Serializable {
+
+
+    default Map getUpdates() {
+        ModifyAttrsRecordHandler handler = (ModifyAttrsRecordHandler) ((ProxyObject) this).getHandler();
+        return handler.getUpdates();
+    }
+
+}
diff --git a/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/util/ClassUtil.java b/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/util/ClassUtil.java
index 6d3911a7f3bbf2a2a8204b5986c673935d144bb7..53cf2a341c42277b78c7a46bc26d213c77a8db48 100644
--- a/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/util/ClassUtil.java
+++ b/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/util/ClassUtil.java
@@ -36,7 +36,9 @@ public class ClassUtil {
     private static final Map CLASS_LAMBDA_CACHE = new ConcurrentHashMap<>();
 
     private ClassUtil() {
-    };
+    }
+
+    ;
 
     /**
      * 返回定义类时的泛型参数的类型. 
@@ -45,11 +47,9 @@ public class ClassUtil { *
* 调用getSuperClassGenricType(getClass(),0)将返回Book的Class类型
* 调用getSuperClassGenricType(getClass(),1)将返回Address的Class类型 - * - * @param clazz - * 从哪个类中获取 - * @param index - * 泛型参数索引,从0开始 + * + * @param clazz 从哪个类中获取 + * @param index 泛型参数索引,从0开始 * @return 返回泛型类型class * @throws IndexOutOfBoundsException */ @@ -74,11 +74,9 @@ public class ClassUtil { /** * 返回接口类的泛型参数的类型 - * - * @param clazz - * 从哪个类中获取 - * @param index - * 泛型参数索引,从0开始 + * + * @param clazz 从哪个类中获取 + * @param index 泛型参数索引,从0开始 * @return 返回class对象 */ public static Class getSuperInterfaceGenericType(Class clazz, int index) { @@ -119,9 +117,8 @@ public class ClassUtil { /** * 是否是数组或结合类 - * - * @param value - * 待检测对象 + * + * @param value 待检测对象 * @return true,是结合或数组 */ public static boolean isArrayOrCollection(Object value) { @@ -212,6 +209,19 @@ public class ClassUtil { return null; } + public static Method findMethod(Class clazz, String methodName, Class[] params) { + if (methodName == null) { + return null; + } + + try { + return clazz.getDeclaredMethod(methodName, params); + } catch (NoSuchMethodException e) { + + return null; + } + } + public static void makeAccessible(Field field) { if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || diff --git a/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/util/ConvertUtil.java b/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/util/ConvertUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..2fd29fd3605911b68141c0e1d4923e624efcaa83 --- /dev/null +++ b/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/util/ConvertUtil.java @@ -0,0 +1,283 @@ +package com.gitee.fastmybatis.core.util; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.Temporal; +import java.util.Date; + +public class ConvertUtil { + + private ConvertUtil() {} + + public static Object convert(Object value, Class targetClass) { + return convert(value, targetClass, false); + } + + public static Object convert(Object value, Class targetClass, boolean ignoreConvertError) { + if (value == null && targetClass.isPrimitive()) { + return getPrimitiveDefaultValue(targetClass); + } + if (value == null || (targetClass != String.class && value.getClass() == String.class + && StringUtil.isBlank((String) value))) { + return null; + } + if (value.getClass().isAssignableFrom(targetClass)) { + return value; + } + if (targetClass == String.class) { + return value.toString(); + } else if (targetClass == Integer.class || targetClass == int.class) { + if (value instanceof Number) { + return ((Number) value).intValue(); + } + return Integer.parseInt(value.toString()); + } else if (targetClass == Long.class || targetClass == long.class) { + if (value instanceof Number) { + return ((Number) value).longValue(); + } + return Long.parseLong(value.toString()); + } else if (targetClass == Double.class || targetClass == double.class) { + if (value instanceof Number) { + return ((Number) value).doubleValue(); + } + return Double.parseDouble(value.toString()); + } else if (targetClass == Float.class || targetClass == float.class) { + if (value instanceof Number) { + return ((Number) value).floatValue(); + } + return Float.parseFloat(value.toString()); + } else if (targetClass == Boolean.class || targetClass == boolean.class) { + String v = value.toString().toLowerCase(); + if ("1".equals(v) || "true".equalsIgnoreCase(v)) { + return Boolean.TRUE; + } else if ("0".equals(v) || "false".equalsIgnoreCase(v)) { + return Boolean.FALSE; + } else { + throw new RuntimeException("Can not parse to boolean type of value: \"" + value + "\""); + } + } else if (targetClass == BigDecimal.class) { + return new BigDecimal(value.toString()); + } else if (targetClass == BigInteger.class) { + return new BigInteger(value.toString()); + } else if (targetClass == byte[].class) { + return value.toString().getBytes(); + } else if (targetClass == Date.class) { + return DateUtil.parseDate(value); + } else if (targetClass == LocalDateTime.class) { + return toLocalDateTime(value); + } else if (targetClass == LocalDate.class) { + return DateUtil.toLocalDate(DateUtil.parseDate(value)); + } else if (targetClass == LocalTime.class) { + return DateUtil.toLocalTime(DateUtil.parseDate(value)); + } else if (targetClass == Short.class || targetClass == short.class) { + if (value instanceof Number) { + return ((Number) value).shortValue(); + } + return Short.parseShort(value.toString()); + } else if (targetClass.isEnum()) { + //TODO 枚举处理 + } + + if (ignoreConvertError) { + return null; + } else { + throw new IllegalArgumentException("Can not convert \"" + value + "\" to type\"" + targetClass.getName() + "\"."); + } + } + + + //Boolean.TYPE, Character.TYPE, Byte.TYPE, Short.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE, Void.TYPE + public static Object getPrimitiveDefaultValue(Class paraClass) { + if (paraClass == int.class || paraClass == long.class || paraClass == float.class || paraClass == double.class) { + return 0; + } else if (paraClass == boolean.class) { + return Boolean.FALSE; + } else if (paraClass == short.class) { + return (short) 0; + } else if (paraClass == byte.class) { + return (byte) 0; + } else if (paraClass == char.class) { + return '\u0000'; + } else { + throw new IllegalArgumentException("Can not get primitive default value for type: " + paraClass); + } + } + + + public static Integer toInt(Object i) { + if (i instanceof Integer) { + return (Integer) i; + } else if (i instanceof Number) { + return ((Number) i).intValue(); + } + return i != null ? Integer.parseInt(i.toString()) : null; + } + + public static Long toLong(Object l) { + if (l instanceof Long) { + return (Long) l; + } else if (l instanceof Number) { + return ((Number) l).longValue(); + } + return l != null ? Long.parseLong(l.toString()) : null; + } + + public static Double toDouble(Object d) { + if (d instanceof Double) { + return (Double) d; + } else if (d instanceof Number) { + return ((Number) d).doubleValue(); + } + + return d != null ? Double.parseDouble(d.toString()) : null; + } + + public static BigDecimal toBigDecimal(Object b) { + if (b instanceof BigDecimal) { + return (BigDecimal) b; + } else if (b != null) { + return new BigDecimal(b.toString()); + } else { + return null; + } + } + + public static BigInteger toBigInteger(Object b) { + if (b instanceof BigInteger) { + return (BigInteger) b; + } + // 数据类型 id(19 number)在 Oracle Jdbc 下对应的是 BigDecimal, + // 但是在 MySql 下对应的是 BigInteger,这会导致在 MySql 下生成的代码无法在 Oracle 数据库中使用 + if (b instanceof BigDecimal) { + return ((BigDecimal) b).toBigInteger(); + } else if (b instanceof Number) { + return BigInteger.valueOf(((Number) b).longValue()); + } else if (b instanceof String) { + return new BigInteger((String) b); + } + + return (BigInteger) b; + } + + public static Float toFloat(Object f) { + if (f instanceof Float) { + return (Float) f; + } else if (f instanceof Number) { + return ((Number) f).floatValue(); + } + return f != null ? Float.parseFloat(f.toString()) : null; + } + + + public static Short toShort(Object s) { + if (s instanceof Short) { + return (Short) s; + } else if (s instanceof Number) { + return ((Number) s).shortValue(); + } + return s != null ? Short.parseShort(s.toString()) : null; + } + + + public static Byte toByte(Object b) { + if (b instanceof Byte) { + return (Byte) b; + } else if (b instanceof Number) { + return ((Number) b).byteValue(); + } + return b != null ? Byte.parseByte(b.toString()) : null; + } + + public static Boolean toBoolean(Object b) { + if (b instanceof Boolean) { + return (Boolean) b; + } else if (b == null) { + return null; + } + + // 支持 Number 之下的整数类型 + if (b instanceof Number) { + int n = ((Number) b).intValue(); + if (n == 1) { + return Boolean.TRUE; + } else if (n == 0) { + return Boolean.FALSE; + } + throw new IllegalArgumentException("Can not support convert: \"" + b + "\" to boolean."); + } + + // 支持 String + if (b instanceof String) { + String s = b.toString(); + if ("true".equalsIgnoreCase(s) || "1".equals(s)) { + return Boolean.TRUE; + } else if ("false".equalsIgnoreCase(s) || "0".equals(s)) { + return Boolean.FALSE; + } + } + + return (Boolean) b; + } + + public static Number toNumber(Object o) { + if (o instanceof Number) { + return (Number) o; + } else if (o == null) { + return null; + } + String s = o.toString(); + return s.indexOf('.') != -1 ? Double.parseDouble(s) : Long.parseLong(s); + } + + + public static Date toDate(Object o) { + if (o instanceof Date) { + return (Date) o; + } + + if (o instanceof Temporal) { + if (o instanceof LocalDateTime) { + return DateUtil.toDate((LocalDateTime) o); + } + if (o instanceof LocalDate) { + return DateUtil.toDate((LocalDate) o); + } + if (o instanceof LocalTime) { + return DateUtil.toDate((LocalTime) o); + } + } + + if (o instanceof String) { + String s = (String) o; + return DateUtil.parseDate(s); + } + + return (Date) o; + } + + + public static LocalDateTime toLocalDateTime(Object o) { + if (o instanceof LocalDateTime) { + return (LocalDateTime) o; + } + if (o instanceof Date) { + return DateUtil.toLocalDateTime((Date) o); + } + if (o instanceof LocalDate) { + return ((LocalDate) o).atStartOfDay(); + } + if (o instanceof LocalTime) { + return LocalDateTime.of(LocalDate.now(), (LocalTime) o); + } + + if (o instanceof String) { + String s = (String) o; + return DateUtil.parseLocalDateTime(s); + } + + return (LocalDateTime) o; + } +} diff --git a/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/util/DateUtil.java b/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/util/DateUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..fd639288805aca7f3d2820d756891aac5329dee3 --- /dev/null +++ b/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/util/DateUtil.java @@ -0,0 +1,272 @@ +package com.gitee.fastmybatis.core.util; + + +import java.sql.Timestamp; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.*; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + + +public class DateUtil { + + private DateUtil() {} + + public static String datePatternWithoutDividing = "yyyyMMdd"; + public static String datePattern = "yyyy-MM-dd"; + public static final String dateMinutePattern = "yyyy-MM-dd HH:mm"; + public static final String dateMinutePattern2 = "yyyy-MM-dd'T'HH:mm"; + public static String datetimePattern = "yyyy-MM-dd HH:mm:ss"; + public static final String dateMillisecondPattern = "yyyy-MM-dd HH:mm:ss SSS"; + public static final String dateCSTPattern = "EEE MMM dd HH:mm:ss zzz yyyy"; + + private static final ThreadLocal> TL = ThreadLocal.withInitial(() -> new HashMap<>()); + + private static final Map dateTimeFormatters = new ConcurrentHashMap<>(); + + public static DateTimeFormatter getDateTimeFormatter(String pattern) { + DateTimeFormatter ret = dateTimeFormatters.get(pattern); + if (ret == null) { + ret = DateTimeFormatter.ofPattern(pattern); + dateTimeFormatters.put(pattern, ret); + } + return ret; + } + + public static SimpleDateFormat getSimpleDateFormat(String pattern) { + SimpleDateFormat ret = TL.get().get(pattern); + if (ret == null) { + if (dateCSTPattern.equals(pattern)) { + ret = new SimpleDateFormat(dateCSTPattern, Locale.US); + } else { + ret = new SimpleDateFormat(pattern); + } + TL.get().put(pattern, ret); + } + return ret; + } + + + public static Date parseDate(Object value) { + if (value instanceof Number) { + return new Date(((Number) value).longValue()); + } + if (value instanceof Timestamp) { + return new Date(((Timestamp) value).getTime()); + } + if (value instanceof LocalDate) { + return DateUtil.toDate((LocalDate) value); + } + if (value instanceof LocalDateTime) { + return DateUtil.toDate((LocalDateTime) value); + } + if (value instanceof LocalTime) { + return DateUtil.toDate((LocalTime) value); + } + String s = value.toString(); + if (StringUtil.isNumeric(s)) { + return new Date(Long.parseLong(s)); + } + return DateUtil.parseDate(s); + } + + + + public static Date parseDate(String dateString) { + if (StringUtil.isBlank(dateString)) { + return null; + } + dateString = dateString.trim(); + try { + SimpleDateFormat sdf = getSimpleDateFormat(getPattern(dateString)); + try { + return sdf.parse(dateString); + } catch (ParseException ex) { + //2022-10-23 00:00:00.0 + int lastIndexOf = dateString.lastIndexOf("."); + if (lastIndexOf == 19) { + return parseDate(dateString.substring(0, lastIndexOf)); + } + + //2022-10-23 00:00:00,0 + lastIndexOf = dateString.lastIndexOf(","); + if (lastIndexOf == 19) { + return parseDate(dateString.substring(0, lastIndexOf)); + } + + //2022-10-23 00:00:00 000123 + lastIndexOf = dateString.lastIndexOf(" "); + if (lastIndexOf == 19) { + return parseDate(dateString.substring(0, lastIndexOf)); + } + + if (dateString.contains(".") || dateString.contains("/")) { + dateString = dateString.replace(".", "-").replace("/", "-"); + return sdf.parse(dateString); + } else { + throw ex; + } + } + } catch (ParseException ex) { + throw new IllegalArgumentException("The date format is not supported for the date string: " + dateString); + } + } + + + + public static LocalDateTime parseLocalDateTime(String dateString) { + if (StringUtil.isBlank(dateString)) { + return null; + } + dateString = dateString.trim(); + DateTimeFormatter dateTimeFormatter = getDateTimeFormatter(getPattern(dateString)); + try { + return LocalDateTime.parse(dateString, dateTimeFormatter); + } catch (Exception ex) { + //2022-10-23 00:00:00.0 + int lastIndexOf = dateString.lastIndexOf("."); + if (lastIndexOf == 19) { + return parseLocalDateTime(dateString.substring(0, lastIndexOf)); + } + + //2022-10-23 00:00:00,0 + lastIndexOf = dateString.lastIndexOf(","); + if (lastIndexOf == 19) { + return parseLocalDateTime(dateString.substring(0, lastIndexOf)); + } + + //2022-10-23 00:00:00 000123 + lastIndexOf = dateString.lastIndexOf(" "); + if (lastIndexOf == 19) { + return parseLocalDateTime(dateString.substring(0, lastIndexOf)); + } + + if (dateString.contains(".") || dateString.contains("/")) { + dateString = dateString.replace(".", "-").replace("/", "-"); + dateTimeFormatter = getDateTimeFormatter(getPattern(dateString)); + return LocalDateTime.parse(dateString, dateTimeFormatter); + } else { + throw ex; + } + } + } + + + private static String getPattern(String dateString) { + int length = dateString.length(); + if (length == datetimePattern.length()) { + return datetimePattern; + } else if (length == datePattern.length()) { + return datePattern; + } else if (length == dateMinutePattern.length()) { + if (dateString.contains("T")) { + return dateMinutePattern2; + } + return dateMinutePattern; + } else if (length == dateMillisecondPattern.length()) { + return dateMillisecondPattern; + } else if (length == datePatternWithoutDividing.length()) { + return datePatternWithoutDividing; + } else if (length == dateCSTPattern.length()) { + return dateCSTPattern; + } else { + throw new IllegalArgumentException("The date format is not supported for the date string: " + dateString); + } + } + + + public static LocalDateTime toLocalDateTime(Date date) { + if (date == null) { + return null; + } + // java.sql.Date 不支持 toInstant(),需要先转换成 java.util.Date + if (date instanceof java.sql.Date) { + date = new Date(date.getTime()); + } + + Instant instant = date.toInstant(); + ZoneId zone = ZoneId.systemDefault(); + return LocalDateTime.ofInstant(instant, zone); + } + + + public static LocalDate toLocalDate(Date date) { + if (date == null) { + return null; + } + // java.sql.Date 不支持 toInstant(),需要先转换成 java.util.Date + if (date instanceof java.sql.Date) { + date = new Date(date.getTime()); + } + + Instant instant = date.toInstant(); + ZoneId zone = ZoneId.systemDefault(); + LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone); + return localDateTime.toLocalDate(); + } + + + public static LocalTime toLocalTime(Date date) { + if (date == null) { + return null; + } + // java.sql.Date 不支持 toInstant(),需要先转换成 java.util.Date + if (date instanceof java.sql.Date) { + date = new Date(date.getTime()); + } + + Instant instant = date.toInstant(); + ZoneId zone = ZoneId.systemDefault(); + LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone); + return localDateTime.toLocalTime(); + } + + + public static Date toDate(LocalDateTime localDateTime) { + if (localDateTime == null) { + return null; + } + ZoneId zone = ZoneId.systemDefault(); + Instant instant = localDateTime.atZone(zone).toInstant(); + return Date.from(instant); + } + + + public static Date toDate(LocalDate localDate) { + if (localDate == null) { + return null; + } + ZoneId zone = ZoneId.systemDefault(); + Instant instant = localDate.atStartOfDay().atZone(zone).toInstant(); + return Date.from(instant); + } + + + public static Date toDate(LocalTime localTime) { + if (localTime == null) { + return null; + } + LocalDate localDate = LocalDate.now(); + LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime); + ZoneId zone = ZoneId.systemDefault(); + Instant instant = localDateTime.atZone(zone).toInstant(); + return Date.from(instant); + } + + + public static String toDateTimeString(Date date) { + return toString(date, datetimePattern); + } + + + public static String toString(Date date, String pattern) { + return date == null ? null : getSimpleDateFormat(pattern).format(date); + } + + +} diff --git a/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/util/StringUtil.java b/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/util/StringUtil.java index 8af0213ac1afd952c829d0c99837bf98f1efec80..a924d7bf55b92eb7dfb86e0b2afb4dac0c18ac78 100644 --- a/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/util/StringUtil.java +++ b/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/util/StringUtil.java @@ -128,4 +128,147 @@ public class StringUtil { } } + /** + * 第一个字符转换为小写 + * + * @param string + */ + public static String firstCharToLowerCase(String string) { + char firstChar = string.charAt(0); + if (firstChar >= 'A' && firstChar <= 'Z') { + char[] arr = string.toCharArray(); + arr[0] += ('a' - 'A'); + return new String(arr); + } + return string; + } + + + /** + * 第一个字符转换为大写 + * + * @param string + */ + public static String firstCharToUpperCase(String string) { + char firstChar = string.charAt(0); + if (firstChar >= 'a' && firstChar <= 'z') { + char[] arr = string.toCharArray(); + arr[0] -= ('a' - 'A'); + return new String(arr); + } + return string; + } + /** + * 驼峰转下划线格式 + * + * @param string + */ + public static String camelToUnderline(String string) { + if (isBlank(string)) { + return ""; + } + int strLen = string.length(); + StringBuilder sb = new StringBuilder(strLen); + for (int i = 0; i < strLen; i++) { + char c = string.charAt(i); + if (Character.isUpperCase(c) && i > 0) { + sb.append('_'); + } + sb.append(Character.toLowerCase(c)); + } + return sb.toString(); + } + + /** + * 下划线转驼峰格式 + * + * @param string + */ + public static String underlineToCamel(String string) { + if (isBlank(string)) { + return ""; + } + String temp = string.toLowerCase(); + int strLen = temp.length(); + StringBuilder sb = new StringBuilder(strLen); + for (int i = 0; i < strLen; i++) { + char c = temp.charAt(i); + if (c == '_') { + if (++i < strLen) { + sb.append(Character.toUpperCase(temp.charAt(i))); + } + } else { + sb.append(c); + } + } + return sb.toString(); + } + + /** + * 字符串为 null 或者内部字符全部为 ' ', '\t', '\n', '\r' 这四类字符时返回 true + */ + public static boolean isBlank(String str) { + if (str == null) { + return true; + } + + for (int i = 0, len = str.length(); i < len; i++) { + if (str.charAt(i) > ' ') { + return false; + } + } + return true; + } + + + /** + * 这个字符串是否是全是数字 + * + * @param str + * @return + */ + public static boolean isNumeric(String str) { + if (isBlank(str)) { + return false; + } + for (int i = str.length(); --i >= 0; ) { + int chr = str.charAt(i); + if (chr < 48 || chr > 57) { + return false; + } + } + return true; + } + public static boolean startsWithAny(String str, String... prefixes) { + if (isBlank(str) || prefixes == null || prefixes.length == 0) { + return false; + } + + for (String prefix : prefixes) { + if (str.startsWith(prefix)) { + return true; + } + } + return false; + } + + + public static boolean endsWithAny(String str, String... suffixes) { + if (isBlank(str) || suffixes == null || suffixes.length == 0) { + return false; + } + + for (String suffix : suffixes) { + if (str.endsWith(suffix)) { + return true; + } + } + return false; + } + + + public static String trimOrNull(String string) { + return string != null ? string.trim() : null; + } + } diff --git a/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/util/UpdateEntity.java b/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/util/UpdateEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..8658346bda412a8ce515cbc51bf574bb9ae2437e --- /dev/null +++ b/fastmybatis-core/src/main/java/com/gitee/fastmybatis/core/util/UpdateEntity.java @@ -0,0 +1,23 @@ +package com.gitee.fastmybatis.core.util; + +import com.gitee.fastmybatis.core.FastmybatisContext; +import com.gitee.fastmybatis.core.update.ModifyAttrsRecordProxyFactory; + +public class UpdateEntity { + + private UpdateEntity() {} + + + + public static T of(Class clazz) { + return ModifyAttrsRecordProxyFactory.getInstance().get(clazz); + } + + public static T of(Class clazz, Object id) { + T newEntity = ModifyAttrsRecordProxyFactory.getInstance().get(clazz); + FastmybatisContext.setPkValue(newEntity,clazz,id); + return newEntity; + } + + +} diff --git a/fastmybatis-demo/fastmybatis-demo-springmvc/pom.xml b/fastmybatis-demo/fastmybatis-demo-springmvc/pom.xml index e50d3eb84d7ae847eeb1114ed2047010a3c3d89b..8e9ad892dbfd7130ddbad57c02a5edc1a96ec8a3 100644 --- a/fastmybatis-demo/fastmybatis-demo-springmvc/pom.xml +++ b/fastmybatis-demo/fastmybatis-demo-springmvc/pom.xml @@ -14,7 +14,7 @@ 1.8 - 5.3.15 + 5.2.15.RELEASE 1.9.7 1.7.33 1.2.79 diff --git a/fastmybatis-demo/fastmybatis-demo-springmvc/src/test/java/com/myapp/TUserMapperTest.java b/fastmybatis-demo/fastmybatis-demo-springmvc/src/test/java/com/myapp/TUserMapperTest.java index 77d5db7911c0a29e0fb3b92e0e762a217439a7e6..a64e102ad38acee8ea719f35a4c2f95352c459dd 100644 --- a/fastmybatis-demo/fastmybatis-demo-springmvc/src/test/java/com/myapp/TUserMapperTest.java +++ b/fastmybatis-demo/fastmybatis-demo-springmvc/src/test/java/com/myapp/TUserMapperTest.java @@ -8,6 +8,7 @@ import com.gitee.fastmybatis.core.query.Query; import com.gitee.fastmybatis.core.query.Sort; import com.gitee.fastmybatis.core.query.annotation.Condition; import com.gitee.fastmybatis.core.query.expression.ValueExpression; +import com.gitee.fastmybatis.core.util.UpdateEntity; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import com.myapp.entity.TUser; @@ -506,6 +507,23 @@ public class TUserMapperTest extends TestBase { print("updateByQuery --> " + i);*/ } + + @Test + public void testForceUpdateById() { + TUser user = UpdateEntity.of(TUser.class,123L); + user.setUsername("李四"); + user.setRemark(null); + int i = mapper.forceUpdateById(user); + print("updateByQuery --> " + i); + + TUser user2 = UpdateEntity.of(TUser.class,123L); +// user.setUsername("李四"); + user2.setRemark(null); + int i2 = mapper.forceUpdateById(user2); + print("updateByQuery2 --> " + i2); + + } + @Test public void testListByPojo() { UserDTO param = new UserDTO();