zoukankan      html  css  js  c++  java
  • 不存在时插入

    不存在时插入

    相信很多朋友写过这样的代码,如果查库发现不存在此条数据,那么就插入当前数据。

    伪代码如下:

    Object obj = this.mapper.selectById(id);
    if(obj==null){
        this.mapper.insert(entity);
    }
    

    这种方式是大家最常用的一种写法。

    但是当出现高并发的情况下就会出现问题:

    假设有两个线程触发当前程序,线程一执行到了第三行,但还未插入;

    线程二执行到第一行刚好执行完查询语句,obj为空。

    此时,线程一就会执行插入语句,而线程二仍然会走到第三行执行插入语句,这样就会导致库里出现两条数据,与我们期望的结果不一样。

    解决方法


    • 最简单一种解决方法:在此段代码块上加锁。
    synchronized (this) {
    	Object obj = this.mapper.selectById(id);
    	if(obj==null){
    		this.mapper.insert(entity);
    	} 
    }
    

    这种方法简单粗暴,但不建议。因为加锁会阻塞进程,降低并发性。

    • 还有一种方法是更改sql语句,在插入时校验数据是否存在。
    -- 当张三的用户信息不存在时,插入张三的个人信息 
    -- 如下 
    -- 插入的数据为select中的'123456','张三','beijing'部分,DUAL 为任意名的关联表用于查询用户是否存在 
    insert into t_user (id,name,address) 
    SELECT
    	'123456','张三','beijing' 
    FROM DUAL WHERE NOT EXISTS 
    ( 
    	SELECT * FROM t_user WHERE name = '张三' 
    )
    

    这种方法借助数据库ACID特性解决了高并发下多次插入的问题。

    编码实现动态sql语句完成不存在时插入


    使用@InsertProvider注解

    mapper如下:

    import com.example.wechat.response.pojo.entity.User;
    import com.example.wechat.response.service.impl.UserSqlProvider;
    import org.apache.ibatis.annotations.InsertProvider;
    import tk.mybatis.mapper.common.Mapper;
    
    /**
     * @author Zzwen
     * @date 2020-12-16 15:08
     */
    @org.apache.ibatis.annotations.Mapper
    public interface UserMapper extends Mapper<User> {
    
        /**
         * 当existedUser不存在时,插入user
         *
         * @param user        要插入的用户信息
         * @param existedUser 要查询的用户信息
         * @return 影响行数
         */
        @InsertProvider(
                type = UserSqlProvider.class,
                method = "insertOnNotExist"
        )
        int insertOnNotExist(User user, User existedUser);
    
    }
    
    

    借助UserSqlProvider的insertOnNotExist方法得到动态sql语句:

    import com.example.wechat.response.pojo.entity.User;
    import org.springframework.stereotype.Service;
    import tk.mybatis.mapper.entity.EntityColumn;
    import tk.mybatis.mapper.entity.EntityTable;
    import tk.mybatis.mapper.mapperhelper.EntityHelper;
    import tk.mybatis.mapper.mapperhelper.SqlHelper;
    
    import java.sql.JDBCType;
    import java.util.Date;
    import java.util.Set;
    
    /**
     * @author Zzwen
     * @date 2020-12-16 15:10
     */
    @Service
    public class UserSqlProvider {
    
        /**
         * 当existedUser不存在时,插入user
         *
         * @param user        要插入的用户信息
         * @param existedUser 要查询的用户信息
         * @return 影响行数
         */
        public String insertOnNotExist(User user, User existedUser) {
            Class entityClass = User.class;
            EntityTable entityTable = EntityHelper.getEntityTable(entityClass);
            StringBuilder sql = new StringBuilder();
    
            sql.append("<script>");
            //插入数据列名
            sql.append("INSERT INTO ").append(entityTable.getName()).append(" ");
            Set<EntityColumn> columnSet = EntityHelper.getColumns(entityClass);
            sql.append("(");
            for (EntityColumn column : columnSet) {
                sql.append(column.getColumn()).append(",");
            }
            sql.deleteCharAt(sql.length() - 1);
            sql.append(")");
    
            //插入数据属性值
            sql.append("SELECT ");
            for (EntityColumn column : columnSet) {
                sql.append("#{user.").append(column.getProperty())
                        .append(",jdbcType=");
                JDBCType jdbcType = getJdbcType(column.getEntityField().getJavaType());
                sql.append(jdbcType).append("},");
            }
            sql.deleteCharAt(sql.length() - 1);
            sql.append(" FROM DUAL WHERE NOT EXISTS");
    
            //数据存在条件
            sql.append("(");
            sql.append("SELECT * ");
            sql.append(SqlHelper.fromTable(entityClass, entityTable.getName()));
            SqlHelper.whereAllIfColumns(entityClass, true, false);
            sql.append("<where>");
            for (EntityColumn column : columnSet) {
                sql.append(SqlHelper.getIfNotNull("existedUser", column, " AND " + column.getColumnEqualsHolder("existedUser"), true));
            }
            sql.append("</where>");
            sql.append(")");
    
            sql.append("</script>");
            return sql.toString();
        }
    
        /**
         * java类型转成Jdbc类型
         *
         * @param javaType Java类型
         * @return jdbc类型
         */
        private JDBCType getJdbcType(Class<?> javaType) {
            if (javaType.equals(Date.class)) {
                return JDBCType.DATE;
            } else if (javaType.equals(Integer.class)) {
                return JDBCType.INTEGER;
            } else if (javaType.equals(Long.class)) {
                return JDBCType.BIGINT;
            } else if (javaType.equals(Boolean.class)) {
                return JDBCType.TINYINT;
            } else if (javaType.equals(String.class)) {
                return JDBCType.VARCHAR;
            }
            return null;
        }
    
    }
    

    上面的getJdbcType方法因为没有找到转换的工具类,所以是自己写了个方法转换的,转换类型并不全,需要自己补充。

    可以参考 https://blog.csdn.net/xxjuanq_only_one/article/details/11936541,继续补充其他类型的转换,或者有工具类就更好了。

    在拼接SQL语句的过程中,借助了tk包中的SqlHelper,里面有包装一些sql语句,可以直接应用。

    最后生成的语句如下:

    <script>
    INSERT INTO user (id,name,address)
    SELECT 
    #{user.id,jdbcType=VARCHAR},#{user.name,jdbcType=VARCHAR},#{user.address,jdbcType=VARCHAR} 
    FROM DUAL WHERE NOT EXISTS
    (
    	SELECT *  FROM user 
    	<where>
    	<if test="existedUser.id != null and existedUser.id != '' "> AND id = 		#{existedUser.id}</if>
    	<if test="existedUser.name != null and existedUser.name != '' "> AND name = #{existedUser.name}</if>
    	<if test="existedUser.address != null and existedUser.address != '' "> AND address = #{existedUser.address}</if>
    	</where>
    )
    </script>
    

    该语句通过mybatis解析成相应的sql语句,就可以达到sql语句完成不存在时插入了。

    应用到的依赖:

    <dependency>
        <groupId>tk.mybatis</groupId>
        <artifactId>mapper-spring-boot-starter</artifactId>
        <version>2.1.3</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.3</version>
    </dependency>
    

    参考资料

  • 相关阅读:
    网络设备安全需求规格
    web安全法则
    Write Hole 问题
    如何同步master的代码到fork分支代码
    Self assignment
    Uninitialized scalar variable
    Operands of different size in bitwise operation
    Insecure Compiler Optimization
    negative array index read
    Unintended sign extension
  • 原文地址:https://www.cnblogs.com/Zzwena/p/14144744.html
Copyright © 2011-2022 走看看