精华内容
下载资源
问答
  • 正如我们所知在mapper文件中用注解的方式一些普通的查询,删除sql语句格式都相对简单,而且一般我们如果有动态sql需要的话,可以采用把sql在xml文件,然后根据Mapper内方法id进行匹配,实现我们复杂的查询或者...

    引言

    正如我们所知在mapper文件中用注解的方式写一些普通的查询,删除sql语句格式都相对简单,而且一般我们如果有动态sql需要的话,可以采用把sql写在xml文件,然后根据Mapper内方法id进行匹配,实现我们复杂的查询或者循环添加等操作。
    但是,本人经历过公司同意要求把sql用注解的方式进行编写,当中不乏一些动态sql的编写,在此做个小总结,希望对大家有帮助。
    首先如果注解写动态sql的话会用到 <script> </scrpit>标签 ! 大家切记 !

    代码展示

    多条件查询以及分页低配版(返回List集合)

    @Select({"<script> SELECT * FROM user WHERE " +
    "<if  test= "mobile != null and mobile != ''"> mobile = #{mobile} </if>" +
    "<if  test= "star != null and star != ''">  AND star=#{star} </if>" +
    "<if test ='startTime != null '>  AND UNIX_TIMESTAMP(update_time) >= #{startTime} </if>" +
    "<if test ='endTime != null '>   AND UNIX_TIMESTAMP(update_time) &lt;= #{endTime} </if>" +
    "<if test="count > 0">  LIMIT #{offset}, #{count} </if> </script>"})
    //参数是对象,携带多参数进行模糊分页查询
    List<User> getUserList(Request request);
    /**
     * request对象携带mobile(电话String),star(分数Byte),startTime(开始时间Long),endTime(结束时间Long),offset(分页参数,偏移量int),count(分页参数,每页返回数据条数)
     * 因为前台传参时startTime和endTime是Long类型参数,所以sql查询是用UNIX_TIMESTAMP()函数处理表内数据使其变为时间戳类型,以此来方便两者比较
     */
    

    多条件查询以及分页升级版(返回List集合)

    //把条件语句单独提出来,方便其他语句使用,比如查询数据集合信息,查询数据条数的sql语句
    String QUERY_CODE_SQL = "<if  test= \"mobile != null and mobile != ''\">  mobile = #{mobile} </if> " +
                "<if  test= \"star != null and star != ''\">  AND star=#{star} </if>" +
                "<if test ='startTime != null '>  AND UNIX_TIMESTAMP(update_time) >= #{startTime} </if>" +
                "<if test ='endTime != null '>   AND UNIX_TIMESTAMP(update_time) &lt;= #{endTime} </if>" +
                "<if test=\"count > 0\">  LIMIT #{offset}, #{count} </if>";
    
    @Select({"<script> SELECT * FROM user WHERE" + QUERY_CODE_SQL + " </script>"})
    //参数是对象,携带多参数进行模糊分页查询集合信息
    List<User> getUserList(Request request);
    
    @Select({"<script> SELECT COUNT(*) FROM user WHERE" + QUERY_CODE_SQL + " </script>"})
    //参数是对象,携带多参数进行模糊查询数据条数
    List<User> getUserList(Request request);
    

    根据某条件循环查询相关信息(返回List集合)

     @Select({"<script> ",
                "SELECT * FROM user ",
                "WHERE id IN ",
                "<foreach collection = 'userIds' separator = ',' open = '(' close = ')' item = 'id'>  ",
                "#{id} ",
                "</foreach> ",
                "</script>"})
    List<User> getUserListByUserIds(@Param("userIds") Set<Long> userIdSet);
    /**
     * 参数为Set集合,集合内携带条件(多个用户的id==userIds)
     * 	collection :collection属性的值有三个分别是List、Array、Map三种,分别对应的参数类型为:List、数组、map集合,我在上面传的参数为Set集合(set内没有重复数据,比List集合实用,避免反复查询),所以值为别名userIds,参数没有别名的话此处用collection;
     *	item : 表示在迭代过程中每一个元素的别名
     *	#{参数} 中的参数名和item别名相对应    #{id} <==> item = 'id'
     *	open :前缀
     *	close :后缀
     */
    

    根据某条件循环查询相关信息(返回Map集合)

    @Select("<script>" +
            "SELECT  * FROM user_info WHERE" + 
            " fellow_id=#{fellow_id} " + 
            " AND user_id IN " + 
            "<foreach collection = 'user_ids' separator = ',' open = '(' close = ')' item = 'user_id'>" + 
            " #{user_id}" + 
            "</foreach>" +
            "</script>")
    @MapKey("userId")
    Map<String, CalendarTask> getCalendarTaskMap(
            @Param("fellow_id") Long fellowId,
            @Param("user_ids") List<Long> userIds);
    /**
     * 参数1是fellowId;参数2是List集合,集合内携带条件(多个用户的user_id==userIds);
     *	@MapKey()注解,我理解的是规定Map集合的key,本map的key就是每一个userId,所以参数是该sql语句查询结果(*查询出的数据有userId字段)的每条数据中的字段值==>userId;
     */
    
    展开全文
  • Java两种动态代理JDK动态代理和CGLIB动态代理

    万次阅读 多人点赞 2018-08-07 15:33:35
    JDK动态代理 cglib动态代理 测试 代理模式 代理模式是23种设计模式的一种,他是指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式。为了对外开放协议,B往往实现了一个接口,A也会去实现接口。但是B是...

    目录

    代理模式

    JDK动态代理

    cglib动态代理

    测试


    代理模式

    代理模式是23种设计模式的一种,他是指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式。为了对外开放协议,B往往实现了一个接口,A也会去实现接口。但是B是“真正”实现类,A则比较“虚”,他借用了B的方法去实现接口的方法。A虽然是“伪军”,但它可以增强B,在调用B的方法前后都做些其他的事情。Spring AOP就是使用了动态代理完成了代码的动态“织入”。

    使用代理好处还不止这些,一个工程如果依赖另一个工程给的接口,但是另一个工程的接口不稳定,经常变更协议,就可以使用一个代理,接口变更时,只需要修改代理,不需要一一修改业务代码。从这个意义上说,所有调外界的接口,我们都可以这么做,不让外界的代码对我们的代码有侵入,这叫防御式编程。代理其他的应用可能还有很多。

    上述例子中,类A写死持有B,就是B的静态代理。如果A代理的对象是不确定的,就是动态代理。动态代理目前有两种常见的实现,jdk动态代理和cglib动态代理。

    JDK动态代理

    jdk动态代理是jre提供给我们的类库,可以直接使用,不依赖第三方。先看下jdk动态代理的使用代码,再理解原理。

    首先有个“明星”接口类,有唱、跳两个功能:

    package proxy;
    
    public interface Star
    {
        String sing(String name);
        
        String dance(String name);
    }
    

    再有个明星实现类“刘德华”:

    package proxy;
    
    public class LiuDeHua implements Star
    {   
        @Override
        public String sing(String name)
        {
             System.out.println("给我一杯忘情水");
    
            return "唱完" ;
        }
        
        @Override
        public String dance(String name)
        {
            System.out.println("开心的马骝");
    
            return "跳完" ;
        }
    }
    

    明星演出前需要有人收钱,由于要准备演出,自己不做这个工作,一般交给一个经纪人。便于理解,它的名字以Proxy结尾,但他不是代理类,原因是它没有实现我们的明星接口,无法对外服务,它仅仅是一个wrapper。

    package proxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class StarProxy implements InvocationHandler
    {
        // 目标类,也就是被代理对象
        private Object target;
        
        public void setTarget(Object target)
        {
            this.target = target;
        }
        
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
        {
            // 这里可以做增强
            System.out.println("收钱");
            
            Object result = method.invoke(target, args);
            
            return result;
        }
        
        // 生成代理类
        public Object CreatProxyedObj()
        {
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
        }  
       
    }
    

    上述例子中,方法CreatProxyedObj返回的对象才是我们的代理类,它需要三个参数,前两个参数的意思是在同一个classloader下通过接口创建出一个对象,该对象需要一个属性,也就是第三个参数,它是一个InvocationHandler。需要注意的是这个CreatProxyedObj方法不一定非得在我们的StarProxy类中,往往放在一个工厂类中。上述代理的代码使用过程一般如下:

    1、new一个目标对象

    2、new一个InvocationHandler,将目标对象set进去

    3、通过CreatProxyedObj创建代理对象,强转为目标对象的接口类型即可使用,实际上生成的代理对象实现了目标接口。

            Star ldh = new LiuDeHua();
    
            StarProxy proxy = new StarProxy();
    
            proxy.setTarget(ldh); 
      
            Object obj = proxy.CreatProxyedObj();
            
            Star star = (Star)obj;

    Proxy(jdk类库提供)根据B的接口生成一个实现类,我们成为C,它就是动态代理类(该类型是 $Proxy+数字 的“新的类型”)。生成过程是:由于拿到了接口,便可以获知接口的所有信息(主要是方法的定义),也就能声明一个新的类型去实现该接口的所有方法,这些方法显然都是“虚”的,它调用另一个对象的方法。当然这个被调用的对象不能是对象B,如果是对象B,我们就没法增强了,等于饶了一圈又回来了。

    所以它调用的是B的包装类,这个包装类需要我们来实现,但是jdk给出了约束,它必须实现InvocationHandler,上述例子中就是StarProxy, 这个接口里面有个方法,它是所有Target的所有方法的调用入口(invoke),调用之前我们可以加自己的代码增强。

    看下我们的实现,我们在InvocationHandler里调用了对象B(target)的方法,调用之前增强了B的方法。

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
        {
            // 这里增强
            System.out.println("收钱");
            
            Object result = method.invoke(target, args);
            
            return result;
        }

    所以可以这么认为C代理了InvocationHandler,InvocationHandler代理了我们的类B,两级代理。

    整个JDK动态代理的秘密也就这些,简单一句话,动态代理就是要生成一个包装类对象,由于代理的对象是动态的,所以叫动态代理。由于我们需要增强,这个增强是需要留给开发人员开发代码的,因此代理类不能直接包含被代理对象,而是一个InvocationHandler,该InvocationHandler包含被代理对象,并负责分发请求给被代理对象,分发前后均可以做增强。从原理可以看出,JDK动态代理是“对象”的代理。

    下面看下动态代理类到底如何调用的InvocationHandler的,为什么InvocationHandler的一个invoke方法能为分发target的所有方法。C中的部分代码示例如下,通过反编译生成后的代码查看,摘自链接地址。Proxy创造的C是自己(Proxy)的子类,且实现了B的接口,一般都是这么修饰的:

    public final class XXX extends Proxy implements XXX
    

    一个方法代码如下:

    
      public final String SayHello(String paramString)
      {
        try
        {
          return (String)this.h.invoke(this, m4, new Object[] { paramString });
        }
        catch (Error|RuntimeException localError)
        {
          throw localError;
        }
        catch (Throwable localThrowable)
        {
          throw new UndeclaredThrowableException(localThrowable);
        }
    

    可以看到,C中的方法全部通过调用h实现,其中h就是InvocationHandler,是我们在生成C时传递的第三个参数。这里还有个关键就是SayHello方法(业务方法)跟调用invoke方法时传递的参数m4一定要是一一对应的,但是这些对我们来说都是透明的,由Proxy在newProxyInstance时保证的。留心看到C在invoke时把自己this传递了过去,InvocationHandler的invoke的第一个方法也就是我们的动态代理实例类,业务上有需要就可以使用它。(所以千万不要在invoke方法里把请求分发给第一个参数,否则很明显就死循环了)

    C类中有B中所有方法的成员变量

      private static Method m1;
      private static Method m3;
      private static Method m4;
      private static Method m2;
      private static Method m0;
    

    这些变量在static静态代码块初始化,这些变量是在调用invocationhander时必要的入参,也让我们依稀看到Proxy在生成C时留下的痕迹。

    static
      {
        try
        {
          m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
          m3 = Class.forName("jiankunking.Subject").getMethod("SayGoodBye", new Class[0]);
          m4 = Class.forName("jiankunking.Subject").getMethod("SayHello", new Class[] { Class.forName("java.lang.String") });
          m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
          m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
          return;
        }
        catch (NoSuchMethodException localNoSuchMethodException)
        {
          throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
        }
        catch (ClassNotFoundException localClassNotFoundException)
        {
          throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
        }
      }
    

    从以上分析来看,要想彻底理解一个东西,再多的理论不如看源码,底层的原理非常重要。

    jdk动态代理类图如下

    cglib动态代理

    我们了解到,“代理”的目的是构造一个和被代理的对象有同样行为的对象,一个对象的行为是在类中定义的,对象只是类的实例。所以构造代理,不一定非得通过持有、包装对象这一种方式。

    通过“继承”可以继承父类所有的公开方法,然后可以重写这些方法,在重写时对这些方法增强,这就是cglib的思想。根据里氏代换原则(LSP),父类需要出现的地方,子类可以出现,所以cglib实现的代理也是可以被正常使用的。

    先看下代码

    package proxy;
    
    import java.lang.reflect.Method;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    public class CglibProxy implements MethodInterceptor
    {
        // 根据一个类型产生代理类,此方法不要求一定放在MethodInterceptor中
        public Object CreatProxyedObj(Class<?> clazz)
        {
            Enhancer enhancer = new Enhancer();
            
            enhancer.setSuperclass(clazz);
            
            enhancer.setCallback(this);
            
            return enhancer.create();
        }
        
        @Override
        public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable
        {
            // 这里增强
            System.out.println("收钱");
            
            return arg3.invokeSuper(arg0, arg2);
        } 
    }
    

    从代码可以看出,它和jdk动态代理有所不同,对外表现上看CreatProxyedObj,它只需要一个类型clazz就可以产生一个代理对象, 所以说是“类的代理”,且创造的对象通过打印类型发现也是一个新的类型。不同于jdk动态代理,jdk动态代理要求对象必须实现接口(三个参数的第二个参数),cglib对此没有要求。

    cglib的原理是这样,它生成一个继承B的类型C(代理类),这个代理类持有一个MethodInterceptor,我们setCallback时传入的。 C重写所有B中的方法(方法名一致),然后在C中,构建名叫“CGLIB”+“$父类方法名$”的方法(下面叫cglib方法,所有非private的方法都会被构建),方法体里只有一句话super.方法名(),可以简单的认为保持了对父类方法的一个引用,方便调用。

    这样的话,C中就有了重写方法、cglib方法、父类方法(不可见),还有一个统一的拦截方法(增强方法intercept)。其中重写方法和cglib方法肯定是有映射关系的。

    C的重写方法是外界调用的入口(LSP原则),它调用MethodInterceptor的intercept方法,调用时会传递四个参数,第一个参数传递的是this,代表代理类本身,第二个参数标示拦截的方法,第三个参数是入参,第四个参数是cglib方法,intercept方法完成增强后,我们调用cglib方法间接调用父类方法完成整个方法链的调用。

    这里有个疑问就是intercept的四个参数,为什么我们使用的是arg3而不是arg1?

        @Override
        public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable
        {
            System.out.println("收钱");
            
            return arg3.invokeSuper(arg0, arg2);
        }

     因为如果我们通过反射 arg1.invoke(arg0, ...)这种方式是无法调用到父类的方法的,子类有方法重写,隐藏了父类的方法,父类的方法已经不可见,如果硬调arg1.invoke(arg0, ...)很明显会死循环。

    所以调用的是cglib开头的方法,但是,我们使用arg3也不是简单的invoke,而是用的invokeSuper方法,这是因为cglib采用了fastclass机制,不仅巧妙的避开了调不到父类方法的问题,还加速了方法的调用。

    fastclass基本原理是,给每个方法编号,通过编号找到方法执行避免了通过反射调用。

    对比JDK动态代理,cglib依然需要一个第三者分发请求,只不过jdk动态代理分发给了目标对象,cglib最终分发给了自己,通过给method编号完成调用。cglib是继承的极致发挥,本身还是很简单的,只是fastclass需要另行理解。

    参考

    https://blog.csdn.net/jiankunking/article/details/52143504

    http://www.php.cn/java-article-407212.html

    https://www.cnblogs.com/chinajava/p/5880887.html

    https://rejoy.iteye.com/blog/1627405

    测试

       public static void main(String[] args)
        {
            int times = 1000000;
            
            Star ldh = new LiuDeHua();
            StarProxy proxy = new StarProxy();
            proxy.setTarget(ldh);
            
            long time1 = System.currentTimeMillis();
            Star star = (Star)proxy.CreatProxyedObj();
            long time2 = System.currentTimeMillis();
            System.out.println("jdk创建时间:" + (time2 - time1));
            
            CglibProxy proxy2 = new CglibProxy();
            long time5 = System.currentTimeMillis();
            Star star2 = (Star)proxy2.CreatProxyedObj(LiuDeHua.class);
            long time6 = System.currentTimeMillis();
            System.out.println("cglib创建时间:" + (time6 - time5));
            
            long time3 = System.currentTimeMillis();
            for (int i = 1; i <= times; i++)
            {
                star.sing("ss");
                
                star.dance("ss");
            }
            long time4 = System.currentTimeMillis();
            System.out.println("jdk执行时间" + (time4 - time3));
            
            long time7 = System.currentTimeMillis();
            for (int i = 1; i <= times; i++)
            {
                star2.sing("ss");
                
                star2.dance("ss");
            }
            
            long time8 = System.currentTimeMillis();
            
            System.out.println("cglib执行时间" + (time8 - time7));   
        }

    经测试,jdk创建对象的速度远大于cglib,这是由于cglib创建对象时需要操作字节码。cglib执行速度略大于jdk,所以比较适合单例模式。另外由于CGLIB的大部分类是直接对Java字节码进行操作,这样生成的类会在Java的永久堆中。如果动态代理操作过多,容易造成永久堆满,触发OutOfMemory异常。spring默认使用jdk动态代理,如果类没有接口,则使用cglib。

    展开全文
  • 正如我们所知在mapper文件中用注解的方式一些普通的查询,删除sql语句格式都相对简单,而且一般我们如果有动态sql需要的话,可以采用把sql在xml文件,然后根据Mapper内方法id进行匹配,实现我们复杂的查询或者...

    正如我们所知在mapper文件中用注解的方式写一些普通的查询,删除sql语句格式都相对简单,而且一般我们如果有动态sql需要的话,可以采用把sql写在xml文件,然后根据Mapper内方法id进行匹配,实现我们复杂的查询或者循环添加等操作。

    但是如果不写xml文件而用@Mapper注解方式的话,那如何写动态SQL?
    首先如果注解写动态sql的话会用到 <script> </scrpit>标签.

    实战例子1:

    1. 多条件查询以及分页低配版(返回List集合)

    @Select({"<script> SELECT * FROM user WHERE " +
    "<if  test= "mobile != null and mobile != ''"> mobile = #{mobile} </if>" +
    "<if  test= "star != null and star != ''">  AND star=#{star} </if>" +
    "<if test ='startTime != null '>  AND UNIX_TIMESTAMP(update_time) >= #{startTime} </if>" +
    "<if test ='endTime != null '>   AND UNIX_TIMESTAMP(update_time) &lt;= #{endTime} </if>" +
    "<if test="count > 0">  LIMIT #{offset}, #{count} </if> </script>"})
    //参数是对象,携带多参数进行模糊分页查询
    List<User> getUserList(Request request);

    2. 多条件查询以及分页升级版(返回List集合)

    //把条件语句单独提出来,方便其他语句使用,比如查询数据集合信息,查询数据条数的sql语句
    String QUERY_CODE_SQL = "<if  test= \"mobile != null and mobile != ''\">  mobile = #{mobile} </if> " +
                "<if  test= \"star != null and star != ''\">  AND star=#{star} </if>" +
                "<if test ='startTime != null '>  AND UNIX_TIMESTAMP(update_time) >= #{startTime} </if>" +
                "<if test ='endTime != null '>   AND UNIX_TIMESTAMP(update_time) &lt;= #{endTime} </if>" +
                "<if test=\"count > 0\">  LIMIT #{offset}, #{count} </if>";
    
    @Select({"<script> SELECT * FROM user WHERE" + QUERY_CODE_SQL + " </script>"})
    //参数是对象,携带多参数进行模糊分页查询集合信息
    List<User> getUserList(Request request);
    

    3. 根据某条件循环查询相关信息(返回List集合)

     @Select({"<script> ",
                "SELECT * FROM user ",
                "WHERE id IN ",
                "<foreach collection = 'userIds' separator = ',' open = '(' close = ')' item = 'id'>  ",
                "#{id} ",
                "</foreach> ",
                "</script>"})
    List<User> getUserListByUserIds(@Param("userIds") Set<Long> userIdSet);
    
    

    上面例子说明如下:

    /**
     * 参数为Set集合,集合内携带条件(多个用户的id==userIds)
     *     collection :collection属性的值有三个分别是List、Array、Map三种,分别对应的参数类型为:List、数组、map集合,我在上面传的参数为Set集合(set内没有重复数据,比List集合实用,避免反复查询),所以值为别名userIds,参数没有别名的话此处用collection;
     *    item : 表示在迭代过程中每一个元素的别名
     *    #{参数} 中的参数名和item别名相对应    #{id} <==> item = 'id'
     *    open :前缀
     *    close :后缀
     */

    4 根据某条件循环查询相关信息(返回Map集合)

    @Select("<script>" +
            "SELECT  * FROM user_info WHERE" + 
            " fellow_id=#{fellow_id} " + 
            " AND user_id IN " + 
            "<foreach collection = 'user_ids' separator = ',' open = '(' close = ')' item = 'user_id'>" + 
            " #{user_id}" + 
            "</foreach>" +
            "</script>")
    @MapKey("userId")
    Map<String, CalendarTask> getCalendarTaskMap(
            @Param("fellow_id") Long fellowId,
            @Param("user_ids") List<Long> userIds);
    
    

     

    上面例子说明如下:

    /**
     * 参数1是fellowId;参数2是List集合,集合内携带条件(多个用户的user_id==userIds);
     *    @MapKey()注解,我理解的是规定Map集合的key,本map的key就是每一个userId,所以参数是该sql语句查询结果(*查询出的数据有userId字段)的每条数据中的字段值==>userId;
     */

    如果XML元素嵌入在<script>XML元素中,则可以在注释值中为动态SQL使用XML元素:

    @Select("<script>SELECT ...</script>")

    但是使用<include>元素会触发SQL Mapper配置解析异常,由以下原因引起:

    org.apache.ibatis.builder.BuilderException: Unknown element in SQL statement. at org.apache.ibatis.scripting.xmltags.XMLScriptBuilder.parseDynamicTags

    如果nodeHandlers在课堂中检查方法org.apache.ibatis.builder.BuilderException,将注意到支持的元素有:

    • trim
    • where
    • set
    • foreach
    • if
    • choose
    • when
    • otherwise
    • bind

    然而,包括基于注释的查询中的片段是不可能的。

    5  在@mapper注释中,动态写SQL的例子

    //bulk insert
        @Insert({"<script> ",
                "insert into CompletedDeviceData (`gatewayID`,`orderId`,`deviceTypeId`,`dataSampledDate`,`data`,`error`)",
                " VALUES ",
                " <foreach collection='dataRecordList' separator=',' index= 'index' item='dataRecord'>",
                "( #{dataRecord.gatewayID},#{dataRecord.orderId},#{dataRecord.deviceTypeId},",
                "#{dataRecord.dataSampledDate},#{dataRecord.data},#{dataRecord.error} )",
                "</foreach> ",
                "</script>"})
        @Options(keyColumn = "recordID", useGeneratedKeys = true)
        int batchInsertRecords(@Param("dataRecordList") List<CompletedDeviceDataEntity> dataRecordList);
    
    
     //bulk update or  insert operation
        @Insert({"<script> ",
                "insert into CompletedDeviceData(`gatewayID`,`orderId`,`deviceTypeId`,`dataSampledDate`,`data`,`error`)",
                "VALUES ",
                " <foreach collection='dataRecordList' separator = ',' index='index' item='dataRecord'>",
                " (#{dataRecord.gatewayID},#{dataRecord.orderId},#{dataRecord.deviceTypeId},",
                " #{dataRecord.dataSampledDate},#{dataRecord.data},#{dataRecord.error})",
                "</foreach> ",
                "ON DUPLICATE KEY UPDATE data=VALUES(data), error=VALUES(error)",
                "</script>"})
        @Options(keyColumn = "recordID", useGeneratedKeys = true)
        int batchUpdateOrInsertRecords(@Param("dataRecordList") List<CompletedDeviceDataEntity> dataRecordList);

    注: VALUES 里面的参数 要用数据库字段来实现对数据的更新,而不是传入的参数字段!

    参考文档:

    1. https://www.cnblogs.com/zjdxr-up/p/8319982.html

    展开全文
  • javascrip动态设置html5页面顶部的跑马灯提示的文字,要动态设置的怎么
  • 当项目慢慢变大,访问量也慢慢变大的时候,就难免的要使用多个数据源和设置读写分离了。...1、对于开发人员,要求serivce类的方法名必须遵守规范,读操作以query、get等开头,操作以update、delete开头。

    当项目慢慢变大,访问量也慢慢变大的时候,就难免的要使用多个数据源和设置读写分离了。

    在开题之前先说明下,因为项目多是使用Spring,因此以下说到某些操作可能会依赖于Spring。

    在我经历过的项目中,见过比较多的读写分离处理方式,主要分为两步:

    1、对于开发人员,要求serivce类的方法名必须遵守规范,读操作以query、get等开头,写操作以update、delete开头。

    2、配置一个拦截器,依据方法名判断是读操作还是写操作,设置相应的数据源。

    以上做法能实现最简单的读写分离,但相应的也会有很多不方便的地方,印象最深的应该是以下几点:

    1、数据源的管理不太方便,基本上只有2个数据源了,一个读一个写。这个可以在spring中声明多个bean来解决该问题,但bean的id和数据源的功能也就绑定了。

    2、因为读写分离往往是在项目慢慢变大后加入的,不是一开始就有,上面说到的第二点方法名可能会各式各样,find、insert、save、exe等等,这些都要一一修改,且要保证以后读的方法名中不能有写操作。也可以拦截的底层一点如JdbcTemplate,但这样会导致交叉设置数据源。

    3、数据源无法动态修改,只能在项目启动时加载。

    以上问题我想开发人员多多少少都会遇到,这也是本文要讨论的问题。

    动态数据源结构

    在我看来一个好的动态数据源,应该跟单数据源一样让使用者感觉不到他是动态的,至少dao层的开发者应该感觉不到。先来看张图:

    dynamicDataSource-diagram

    看了上图应该就明白我的思路了,对使用者来说只有一个数据源DynamicDataSource,下面我们来谈谈如何实现它。

    基于spring实现动态数据源

    其实spring早就想到了这一点,也已经为我们准备好了扩展类AbstractRoutingDataSource,我们只需要一个简单的实现即可。网上关于这个类文章很多,但都比较粗浅没有讲到点子上,只是实现了多个数据源而已。

    这里我们同样来实现AbstractRoutingDataSource,它只要求实现一个方法:

    1. /**
    2. * Determine the current lookup key. This will typically be
    3. * implemented to check a thread-bound transaction context.
    4. * <p>Allows for arbitrary keys. The returned key needs
    5. * to match the stored lookup key type, as resolved by the
    6. * {@link #resolveSpecifiedLookupKey} method.
    7. */
    8. protected abstract Object determineCurrentLookupKey();

    你可以简单的理解它:spring把所有的数据源都存放在了一个map中,这个方法返回一个key告诉spring用这个key从map中去取。

    它还有个targetDataSourcesdefaultTargetDataSource属性,网上的一堆做法是继承这个类,然后在声明bean的时候注入dataSource:

    1. <bean id="dynamicdatasource" class="......">
    2. <property name="targetDataSources">
    3. <map>
    4. <entry key="dataSource1" value-ref="dataSource1" />
    5. <entry key="dataSource2" value-ref="dataSource2" />
    6. <entry key="dataSource3" value-ref="dataSource3" />
    7. </map>
    8. </property>
    9. <property name="defaultTargetDataSource" ref="dataSource1" />
    10. </bean>

    这样虽然简单,但是弊端也是显而易见的,除了使用了多个数据源之外没有我们想要的任何操作。但是如果不配置targetDataSources,spring在启动的时候就会抛出异常而无法运行。

    其实我们完全可以在spring启动的时候,我们自己来解析数据源,然后把解析出并实例化的dataSource设置到targetDataSources。下面是解析的核心代码,数据源配置文件的格式可以看这里:数据源配置样例

    1. /**
    2. * 初始化数据源
    3. *
    4. * @param dataSourceList
    5. */
    6. public void initDataSources(List<Map<String, String>> dataSourceList) {
    7. LOG.info("开始初始化动态数据源");
    8. readDataSourceKeyList = new ArrayList<String>();
    9. writeDataSourceKeyList = new ArrayList<String>();
    10. Map<Object, Object> targetDataSource = new HashMap<Object, Object>();
    11. Object defaultTargetDataSource = null;
    12. for (Map<String, String> map : dataSourceList) {
    13. String dataSourceId = DynamicDataSourceUtils.getAndRemoveValue(map, ATTR_ID,
    14. UUIDUtils.getUUID8());
    15. String dataSourceClass = DynamicDataSourceUtils
    16. .getAndRemoveValue(map, ATTR_CLASS, null);
    17. String isDefaultDataSource = DynamicDataSourceUtils.getAndRemoveValue(map,
    18. ATTR_DEFAULT, "false");
    19. String weight = DynamicDataSourceUtils.getAndRemoveValue(map, DS_WEIGHT, "1");
    20. String mode = DynamicDataSourceUtils.getAndRemoveValue(map, DS_MODE, "rw");
    21. DataSource dataSource = (DataSource) ClassUtils.newInstance(dataSourceClass);
    22. DynamicDataSourceUtils.setDsProperties(map, dataSource);
    23. targetDataSource.put(dataSourceId, dataSource);
    24. if (Boolean.valueOf(isDefaultDataSource)) {
    25. defaultTargetDataSource = dataSource;
    26. }
    27. DynamicDataSourceUtils.addWeightDataSource(readDataSourceKeyList,
    28. writeDataSourceKeyList, dataSourceId, Integer.valueOf(weight), mode);
    29. LOG.info("dataSourceId={},dataSourceClass={},isDefaultDataSource={},weight={},mode={}",
    30. new Object[] { dataSourceId, dataSourceClass, isDefaultDataSource, weight, mode });
    31. }
    32. this.setTargetDataSources(targetDataSource);
    33. if (defaultTargetDataSource == null) {
    34. defaultTargetDataSource = (CollectionUtils.isEmpty(writeDataSourceKeyList) ? targetDataSource
    35. .get(readDataSourceKeyList.iterator().next()) : targetDataSource
    36. .get(writeDataSourceKeyList.iterator().next()));
    37. }
    38. this.setDefaultTargetDataSource(defaultTargetDataSource);
    39. super.afterPropertiesSet();
    40. LOG.info("初始化动态数据源完成");
    41. }

    在解析出来之后,我们调用父类的this.setTargetDataSources(targetDataSource);this.setDefaultTargetDataSource(defaultTargetDataSource);方法将它们存入进去。而dataSource的key则根据读写和权重的不同,分别保存到readDataSourceKeyListwriteDataSourceKeyList

    那么什么时候来运行这个解析的方法呢?有些同学可能一下就想到了spring声明bean时的init-method属性,但是这里不行。因为init-method是在bean初始化完成之后调用的,当spring在初始化DynamicDataSource时发现这两个属性是空的异常就抛出来了,根本就没有机会去运行init-method

    所以我们要在bean的初始化过程中来解析并存入我们的数据源。要实现这个操作,我们可以实现spring的InitializingBean接口。由于AbstractRoutingDataSource已经实现了该接口,我们只需要重写该方法就行。也就是说DynamicDataSource要实现以下两个方法:

    1. @Override
    2. protected Object determineCurrentLookupKey() {
    3. ...
    4. }
    5. @Override
    6. public void afterPropertiesSet() {
    7. this.initDataSources();
    8. }

    afterPropertiesSet方法中实现我们解析数据源的操作。但是这样还不够,因为spring容器并不知道你做了这些,所以最后的一行super.afterPropertiesSet();千万别忘了,用来通知spring容器。

    到这里数据源的解析已经完成了,我们又怎么样来取数据源呢?

    这个我们可以利用ThreadLocal来实现。编写DynamicDataSourceHolder类,核心代码:

    1. private static final ThreadLocal<DataSourceContext> DATASOURCE_LOCAL = new ThreadLocal<DataSourceContext>();
    2. /**
    3. * 设置数据源读写模式
    4. *
    5. * @param isWrite
    6. */
    7. public static void setIsWrite(boolean isWrite) {
    8. DataSourceContext dsContext = DATASOURCE_LOCAL.get();
    9. //已经持有且可写,直接返回
    10. if (dsContext != null && dsContext.getIsWrite()) {
    11. return;
    12. }
    13. if (dsContext == null || isWrite) {
    14. dsContext = new DataSourceContext();
    15. dsContext.setIsWrite(isWrite);
    16. DATASOURCE_LOCAL.set(dsContext);
    17. }
    18. }
    19. /**
    20. * 获取dsKey
    21. *
    22. * @return
    23. */
    24. public static DataSourceContext getDsContent() {
    25. return DATASOURCE_LOCAL.get();
    26. }

    只有简单的设置读写模式和获取dataSource的key。

    动态数据源”读已之所写”问题

    在设置读写模式时需要注意,如果当前线程已经拥有数据源了且是可写的,则直接返回使用当前的数据源。这是一个简单的操作却会影响到整个项目。为什么要这样做呢?要是我方法中写操作后有读操作不是也用写数据源了?没错!

    这涉及到一个多数据源主从同步时的读已之所写问题,这里简单的来讲解一下。

    数据库主从同步时,事务一般分两种:

    1、硬事务,当往数据库保存数据时,程序读到所有数据库的数据都是一致的,但相应的性能会变低,如果数据库操作时间较长,有可能会引起线程阻塞。

    2、软事务,当往数据库保存数据时,程序读到的数据不一定是一致的,但最终是一致的。举个例子,当你往主库(写库)存入数据时,数据可能无法实时同步到从库(读库),这中间可能会有那么几秒钟的误差,如果这时候刚好读到这批数据,数据就是不一致的。

    当数据库都要分主从和读写分离了,肯定是有性能压力了,所以大多数都会选择第二种(只是大部分不是绝对,银行等机构可能会第一种)。

    这时候数据就会有一个实时展示的问题了。以当前较流行的微信朋友圈为例,我自己发表了一条朋友圈动态,肯定是希望能够马上看到,如果隔个三五秒才能显示我会怀疑是不是发布失败了?用户体验感也会直线下降。但对别人来说,就算时时关注着我也不会知道我这个时候发布了动态,迟个三五秒显示并无大碍,对整个系统也没有影响。

    说到这里相信应该已经明白了吧,简单说就是自己写的数据要能够马上读到,这就是原因了。

    指定了读写模式,接下来就是获取数据源了。代码:

    1. @Override
    2. protected Object determineCurrentLookupKey() {
    3. DataSourceContext dsContent = DynamicDataSourceHolder.getDsContent();
    4. //已设置过数据源,直接返回
    5. if (StringUtils.isNotBlank(dsContent.getDsKey())) {
    6. return dsContent.getDsKey();
    7. }
    8. if (dsContent.getIsWrite()) {
    9. String dsKey = writeDataSourceKeyList.get(RandomUtils.nextInt(writeDataSourceKeyList
    10. .size()));
    11. dsContent.setDsKey(dsKey);
    12. } else {
    13. String dsKey = readDataSourceKeyList.get(RandomUtils.nextInt(readDataSourceKeyList
    14. .size()));
    15. dsContent.setDsKey(dsKey);
    16. }
    17. if (LOG.isDebugEnabled()) {
    18. LOG.debug("当前操作使用数据源:{}", dsContent.getDsKey());
    19. }
    20. return dsContent.getDsKey();
    21. }

    这里同样注意如果已经设置过数据源了,直接返回,这样就能保证当前线程用的始终是同一个数据源(读改写时会变化一次)。

    如果未设置过数据源则根据读写模式,随机的从key列表中取一个使用。为什么要随机呢?这就牵扯到具体的权重实现了。

    动态数据源权重实现

    这里的权重实现十分简单,也是当前很多组件的权重实现方式。假设一个读dataSource的权重是5,则相应的往readDataSourceKeyList中存入5个key,写dataSource也一样,读写则两边都存。这样根据权重的不同key列表中存入的数量也就不尽相同,取时生成一个小于列表大小的随机数随机取一个就行了。

    使用拦截器设置读写模式

    各个组件的功能都实现了,只差东风了,什么时候来设置读写模式呢?

    这个简单,使用一个拦截器就能搞定。因为是基于Spring JdbcTemplate,所以只要拦截相应的方法即可。JdbcTemplate的方法命名还是十分规范的,开发人员改动的可能性也几乎为零,这里我们拦截接口:

    1. /**
    2. * 动态数据源拦截器
    3. *
    4. * Created by liyd on 2015-11-2.
    5. */
    6. @Aspect
    7. @Component
    8. public class DynamicDsInterceptor {
    9. @Pointcut("execution(* org.springframework.jdbc.core.JdbcOperations.*(..))")
    10. public void executeMethod() {
    11. }
    12. @Around("executeMethod()")
    13. public Object methodAspect(ProceedingJoinPoint pjp) throws Throwable {
    14. String methodName = pjp.getSignature().getName();
    15. if (StringUtils.startsWith(methodName, "query")) {
    16. DynamicDataSourceHolder.setIsWrite(false);
    17. } else {
    18. DynamicDataSourceHolder.setIsWrite(true);
    19. }
    20. return pjp.proceed();
    21. }
    22. }

    动态修改数据源

    到这里我们的动态数据源就实现的差不多了,有的同学可能会问,那我怎么动态的去修改它呢?

    其实看到上面的initDataSources方法答案就已经有了,它的参数是 List<Map<String, String>> dataSourceList,只需要将数据源的参数封装成map的list传入调用该方法就能实现动态修改了,这也是我为什么把super.afterPropertiesSet();这一行放到这里面而不是重写方法本身的原因。以下是一个简单的候示例:

    1. List<Map<String, String>> dsList = new ArrayList<Map<String, String>>();
    2. Map<String, String> map = new HashMap<String, String>();
    3. map.put("id", "dataSource4");
    4. map.put("class", "org.apache.commons.dbcp.BasicDataSource");
    5. map.put("default", "true");
    6. map.put("weight", "10");
    7. map.put("mode", "rw");
    8. map.put("driverClassName", "com.mysql.jdbc.Driver");
    9. map.put("url",
    10. "jdbc:mysql://localhost:3306/db1?useUnicode=true&amp;characterEncoding=utf-8");
    11. map.put("username", "root");
    12. map.put("password", "");
    13. dsList.add(map);
    14. dynamicDataSource.initDataSources(dsList);

    在实际的场景中,根据项目使用技术的不同,你可以使用监听器、socket、配置中心等来实现该数据源动态修改的功能,只要保存调用initDataSources方法时传入的数据源信息是正确的就可以了。

    动态数据源的实现就到这里了,我希望更多的是提供了一种思维,可以根据这个思维做些改变将它应用到具体的场景中,而不仅仅限于JdbcTemplate和Spring,只是做了一个抛砖引玉而已。

    所有的源码都可以在上方供下载的dexcoder-assistant工具包中找到,欢迎各位讨论,留下自己的意见和想法。

    展开全文
  • 文章目录最终效果前面环境准备(Maven的)编写自己的日志记录和方法增强处理1. 创建自己的切面接口和实现类2. 创建我的代理类3. 创建自己的日志类4. 创建被代理类接口和其实现5. 创建测试类6. 这样就完成了一个...
  • 讲真的,我在这个脚本的时候,我还没把python的基本语法都了解了。我只能说,语言不重要,好用就行。...1. 获取所有的动态信息 2. 获取删除的url,遍历已经获取的所有动态,然后全部删除了就行。 另...
  • //动态链表 //每次建立新结点的时候都认为新结点是链 //如果再开辟内存时,那么之前的结点的next域就会改变值,指向新开辟的结点,而此时新开辟的结点又变成链尾 struct Student { int num; float score; }; ...
  • 一个不能动态扩容的rpc框架是一个残缺的框架。本次优化:引入注册中心概念、实现服务的动态扩容、负载均衡等功能。
  • C#中动态读写App.config配置文件

    千次阅读 2011-09-02 10:16:36
    c#,WinForm中读写配置文件App.config  2010-04-24 16:00:49| 分类: C# | 标签: |字号大中小 订阅 ...在.Net中提供了配置文件,让我们可以很方面的处理配置信息,这个配置是XML格式的。
  • 不要死!天猫App的动态化配置中心实践 2016-03-15 高嘉峻 移动开发前线 版权声明 本文为天猫无线技术专家、苹果核博主高嘉峻的投稿,未经授权禁止转载 不要死,一个永恒...
  • 动态添加 表头 headList --> 所有表头行集合 List<List<String>> headList = new ArrayList<List<String>> (); // 第 n 行 的表头 List<String> headTitle0 = new ArrayList (); List <String> head...
  • C++动态链表实现学生信息管理系统

    千次阅读 多人点赞 2018-09-24 08:37:44
     我是用动态链表处理这些学生信息的,每次都从存储文件中读取学生信息,用动态链表串起来,处理过后再存回文件中。所以这篇文章可以帮助大家复习动态链表。我们一个功能一个功能地来讲述:  ...
  • 自己动手做动态网站并发布遇到的问题(给刚接触动态网站的初学者) 所需工具:  硬件:笔记本一台。  系统:win7  软件:microsoft office access2007或2003; Dreamweaver cs5; iis7; 花生壳  放寒假的...
  • 很多时候Android安装是不会主动提示需要权限的 需要手动获取 ... //6.0之后要动态获取权限,重要!!! protected void judgePermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { ...
  • 的一个Android社交类APP常用的动态消息发布模板,作为备忘和参考,在此记下。 社交类APP用户通常会发布一些信息(一般考虑装载到Android的ListView),每一条信息一般有图(ImageView)和文(TextView)组成。...
  • 一个函数insert,用来向一个动态链表插入节点。 方法一: #include <stdio.h> #include <stdlib.h> struct Student { //声明结构体(双向链表结构体) int num; float score; struct Student *...
  • 基于Springboot执行多个定时任务并且动态获取定时任务信息 基于Springboot执行多个定时任务并且动态获取定时任务信息 简介 说明 SpringApplication-启动类 动态获取定时任务信息 mapper service service impl ...
  • Apk动态写入信息

    千次阅读 2016-05-27 17:29:52
    曾几何时有这么一个需求,将apk(游戏或者应用)投放不同的平台倒量,服务端需要统计用户来自于某个平台的apk,由于...所以是否有一种可以动态往apk中写入信息,而不用重新打包的方法呢?答案是肯定的: 第一种方案:
  • 当数组中每个元素都只带有一...使用数组的方式大规模处理数据信息,那么,如何运用动态数组呢? 动态数组是指在声明时没有确定数组大小的数组,即忽略圆括号中的下标;当要用它时,可随时重新指出数组的大小。使用动
  • SpringBoot2.0_MQTT消息订阅之动态Topic

    万次阅读 多人点赞 2019-06-05 16:06:09
    之前总结过两篇关于MQTT消息发布及消息订阅的文章,很多朋友都觉得不错,不过也有些小疑问,特别是关于mqtt消息订阅的,如何在项目应用中,做到动态添加Topic及监听消息?很多朋友对此都有疑问,却又不知如何下手,...
  • ajax轮询实用例子之动态更新消息

    千次阅读 2017-08-26 17:22:14
    本文得有点含糊,主要是在index.jsp实现新消息实时提醒功能,用到了ajax轮询(即隔一段时间发送请求),后台查询响应( 数据库查询新消息数目,然后返回数据,再插入index.js[中实现更新操作 ) ...
  • 1. 动态表示 # include # include # define LEN sizeof(Node) typedef struct Node { long num ; float score ; struct Node * next ; } * Linklist , Node ; int n ; //全局...
  • WinCC 添加动态报警消息文本

    千次阅读 2019-06-26 17:36:20
    WinCC 添加动态报警消息文本 添加报警变量。 在过程值块中勾选要添加几个过程值。 此时,在消息的错误组中,可以看到新增了几列过程值。 绑定过程值对应的变量。 添加消息文本:报警@1%2d@报警@2%2d@报警@3%2d@ ...
  • Vue iview 动态显示表头信息

    千次阅读 2019-09-05 16:53:43
    Vue iview 动态显示表头信息遇到问题固定表头表示动态表头表示 遇到问题 大部分遇到的表头都是固定的,但如果要求后端给你什么表头信息你就显示什么呢?接下来告诉你方法!!! 固定表头表示 一般Vue静态页面Table为...
  • 今天终于完成了第一个php做出来的动态网页。总结一下第一:先用html一个以表单为主的网页。在表单时先需要注意提交方式,一般有post和get两种。post一般用于数据量较大的数据提交时。get数据量较小。在提取表单...
  • 做了几年的iOS开发一直没有博客,一直怕的不好误导大家,今儿第一次在csdn点干货,代码下载地址https://github.com/zhulang/messageForward 实现动态添加方法 (1)为了编译通过,我们需要通过performSelector...
  • 一个函数建立一个有3名学生数据的单向动态链表。 解:程序:#include#include#define LEN sizeof(struct Student)struct Student{ long num; float score; struct Student *next;};int n;struct Student *creat...
  • Qt 实现消息和槽动态绑定

    千次阅读 2018-03-27 17:50:33
    前一段时间过Qt通过反射和配置文件实现对象动态创建的例子,下面下对象和槽函数之间实现动态绑定: 下面贴三段代码: 第一段槽函数实现类注册 WorkSpaceWidget::WorkSpaceWidget(QWidget *p) { ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 891,802
精华内容 356,720
关键字:

动态消息怎么写