myorm【重点】

work log,2020.6.23迁移至此

 

1类加载器自定义,解决jdbc jar包冲突;testcase

2基础类型crud:oracle日期插入,sequence,秒数据截断,oracle日期类型接收,BigDecimal类型转换

3join,循环依赖,借鉴spring ioc

4logback集成自定义类加载器

5threadlocal支持事务,包括oracle;select可重复读

6时区

7对业务层无感的testcase体系

8日志统一实践

9oracle主键回写

10事务支持隔离级别和只读

11结合自定义注解的事务动态代理切面,支持spring与guice(扫描service与dao包,扫到注解,则将代理注入容器),支持特定异常不回滚;被代理类的依赖对象运行期注入

12事务不能传播导致的问题推测

13事务结束时,修复autocommit和readonly,忽略隔离级别

14事务的简单传播,start,end,commit,rollback

15乐观锁

16join循环依赖缓存自动清除

17多数据源,mysql8必要参数时区伪装

18多数据源事务切面产生问题

19alias jdbc处理

20多数据源支持多时区,自定义注解+运行期类型反射识别

21多数据源支持多事务管理

22多数据源session继承关系梳理

23修复事务切面,代理类非代理方法,反射调用时的原始异常暴露

24queryForObject.clone 处理NO_FIELD 由于循环依赖的预缓存机制导致被覆盖问题

25 事务代理、synchronized、db rc及以上隔离级别 三者的矛盾

26 迁移到mybatis ,前后2个方案;mybatis orm低侵入整体解决方案

27 javassist运行期加载前篡改字节码

28 动态代理究竟会不会遗失被代理类,field和method上的注解

29 mybatis-guice源码,事务代理切面

30 aop代理,入站出站的log deprecated

31 夏令时冬令时发现

32 夏令时冬令时解决方案 insert / update

33 夏令时冬令时解决方案 select

34 自定义动态代理切面ioc补偿性能

35 接11,结合自定义注解的事务动态代理切面(cglib)

36 静态资源基础数据加载

37 夏令时冬令时解决方案 select 修复,完美解决收官

38 mybatis-guice运行期修改数据源 mybatis guice 事务代理切面 5

#主导抽象ceftable重构

39 cglib asm冲突解决

40 接35,结合自定义注解的事务动态代理切面(cglib切子函数,真正的cglib) 当动态代理遇到ioc (四)真正的cglib

41 代理侵入框架与guice  

42 一些java 日志实践 2021.2.10,此后若无特殊情况,不再在正文标注,仅在这个目录标注

43 当动态代理遇到ioc (五)使用cglib切面与自定义类加载器构建独有环境aop日志  2021.2.10

44 当动态代理遇到ioc (四)真正的cglib 8 事务切面调整为cglib后,一个大bug 2021.4.3 ~ 2021.4.28

45 当动态代理遇到ioc (四)真正的cglib 10 食物切面支持按名称getBeanfromguice 2021.4.30

 

46 2021.7.27 timecut/cache

使用threadlocal

 

47 jdk/cglib 继承抽象重构 2022.6.24 transaction 继承备案

48 cglib支持继承类 当动态代理遇到ioc (四)真正的cglib 11 2022.7.16

49 当动态代理遇到ioc (四)真正的cglib 12 cglib遇上debug的诡异问题 2022.7.16

50  transaction继承备案2 多数据源 mybatis guice 事务代理切面 6 多数据源事务 2022.7.25 

 

 

=================正文

2020.2.18

spring+jdbc+myclassloader+pool2+junit    

1)类加载器隔离朴实案例 意图、实现、问题

 

 

2)

//@ImportResource("testspringxml.xml")  跑testcase junit时提示找不到testspringxml.xml
@ImportResource("classpath:testspringxml.xml")

 

    @Bean
    @Qualifier("sybaseJdbcNativePool")
    // 注入工场的名字是下面这个,上面这个只对autowired有效,对getbean无效
    public  GenericObjectPool<AbstractJdbcUtil> sybaseJdbcNativePool(@Autowired SybaseProperties sybaseProperties) {
   

3)jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1

4)如果使用spring boot 的 ApplicationContext 及 getbean中不实现~Aware的方式,运行test时Context注入失败

 

5)AbstractJdbcUtil中

    static {
        DriverManager.setLoginTimeout(5);
    }

 jdbc超时设置

    public Connection getConnection() {
        if (conn == null) {
            synchronized (this) {
                if(conn == null) {
                    try {
                        String proxyName = "JdbcProxy";
                        if(classLoader == ClassLoader.getSystemClassLoader()) {
                            proxyName = JdbcProxy.class.getName();
                        }

                        Class.forName(driver, true, classLoader);
                        Class proxy = classLoader.loadClass(proxyName);
                        classLoader.loadClass(proxyName);
                        Method method = proxy.getMethod("getConnection", String.class, String.class, String.class);
                        conn = (Connection)method.invoke(null, url, user, pwd);
                        //    conn = DriverManager.getConnection(url, user, pwd);
                    } catch (InvocationTargetException e) {
                        throw new DBException(e.getTargetException());
                    } catch (Exception e) {
                        throw new DBException(e);
                    }
                }
            }

        }
        return conn;
    }

 e.getTargetException,与超时设置两者配合,爆出:

java.sql.SQLException: JZ00M: Login timed out. Check that your database server is running on the host and port number you specified. Also, check the database server for other conditions (such as a full tempdb) that might be causing it to hang.

com...trending.config.jdbc.DBException: java.sql.SQLException: JZ00M: Login timed out. Check that your database server is running on the host and port number you specified. Also, check the database server for other conditions (such as a full tempdb) that might be causing it to hang.
	at com...trending.config.jdbc.AbstractJdbcUtil.getConnection(AbstractJdbcUtil.java:64) ~[classes/:na]

  此处的连接url用了xxx,是真的xxx哦,然后到时间爆了超时而不是直接连接异常抛出,jdbc是jconn3-6.0.jar,sybase

 

2020.2.27 orm

0)背景:类加载器隔离朴实案例

1)field反射要注意:getDeclaredField和getField的区别

2)field反射要注意一些静态成员变量,测试时加静态变量干扰

3)仅支持单列主键,不支持bean继承     

4)映射类型有待完善

5)自定义类加载的类类型比较类的相同通过对是否为同一个类加载器进行判断中的2、3

6)/

7)使用oracle sequence主键自增策略

new StringBuilder("select ").append(sequenceName).append(".nextval from sys.dual")

8)oracle插入、更新日期需要to_date函数(秒)to_timestamp(毫秒),不像mysql,直接给字符串

SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS");

//to_date('2019-06-06 23:44:43', 'yyyy-mm-dd hh24:mi:ss')

to_timestamp('2012-5-13 18:45:34.567','yyyy-mm-dd hh24:mi:ss:ff3') db类型为timestamp(6)

 

9)java.sql.timestamp extends java.util.Date,所以mysql的datetime和oracle的timestamp(6)在jdbc层面都用java.sql.timestimp接收,然后再放到java.util.Date类型字段中没出错,BigDecimal(oracle的number类型、mysql的decimal类型使用的jdbc层接收类型)到Double就不行,要显式转换,第5)点中有详细代码参考

 

10)秒级数据截断

//                        Class oracleDate = getOracleJdbcClassLoader().loadClass(oracle.sql.TIMESTAMP.class.getName());
//                        System.out.println(System.identityHashCode(oracle.sql.TIMESTAMP.class));
//                        System.out.println(System.identityHashCode(oracleDate));
//                        System.out.println(System.identityHashCode(val.getClass()));
//                        if(val.getClass() == oracleDate) {
//                            if(fcl == Date.class) {
//                                Method method = oracleDate.getMethod("timestampValue");
//                                val = method.invoke(val);
//                                if(cutMilliSecond) {
//                                    Date temp = (Date)val;
//                                    val = new Date(temp.getTime() / 1000 * 1000);
//                                }
//                            }
//                        }

 

2020.3.20

orm join

1)select oneone manyone onemany

insert update oneone,manyone支持从bean取出id插入更新,onemany不支持也不需要

insert update delete不支持联合操作

 

2)2020.3.23 oracle.sql.BLOB

                        Class oracleBlob = getOracleJdbcClassLoader().loadClass(oracle.sql.BLOB.class.getName());
                        if(val.getClass() == oracleBlob) {
                            if(fcl == byte [].class) {
                                InputStream is = null;
                                byte[] b = null;
                                Method method = oracleBlob.getMethod("getBinaryStream");
                                Method methodLength = oracleBlob.getMethod("length");
                                try {
                                    is = (InputStream)method.invoke(val);
                                    if(is == null) break;
                                    b = new byte[((Long) methodLength.invoke(val)).intValue()];
                                    is.read(b);
                                } catch (Exception e) {
                                    throw new DBException("fail to read oracle blob", e);
                                } finally {
                                    try {
                                        is.close();
                                        is = null;
                                    } catch (IOException e) {
                                       ;
                                    }
                                }
                                val = b;
                            }
                        }

 

3)2020.3.24-25 OneToMany ManyToOne循环依赖,同样适用于OneToOne之间,参照spring ioc循环依赖的解决方案:spring 循环依赖,构造函数注入与setter注入(属性注入)有什么优劣,三种注入方式又有什么优劣 

 

 

4)这种循环依赖的bean作为rest返回值,会导致json序列化失败,只能在debug模式内存调试 

 

2020.3.27

类加载器隔离朴实案例(二)logback

 

2020.4.7

原理:事务的原理 学习笔记

 

private static final ThreadLocal<AbstractJdbcUtil> utilThreadLocal = new ThreadLocal<>();
 
public void startTransaction() {
    AbstractJdbcUtil util = null;
    try {
        util = getJdbcPool().borrowObject();
        utilThreadLocal.set(util);  【重要,即使后面两句报错,也能在finally中的endTransaction将连接还给连接池,不造成泄漏】
        Connection connection = util.getConnection();
        connection.setAutoCommit(false);
    } catch (Exception e) {
        throw new DBException("start transaction error");
    }
}
 
public void commit() {
    try {
        utilThreadLocal.get().getConnection().commit();
    } catch (Exception e) {
        throw new DBException("transaction commit error");
    }
}
 
public void rollback() {
    try {
        utilThreadLocal.get().getConnection().rollback();
    } catch (Exception e) {
        throw new DBException("transaction rollback error");
    }
}
 
public void endTransaction() {
    if(utilThreadLocal.get() != null) {
        getJdbcPool().returnObject(utilThreadLocal.get());
        utilThreadLocal.remove();
    }
}

 

        try{
            scefOrmSession.startTransaction();
            // some insert, update or delete here
            scefOrmSession.commit();
        } catch (Exception e) {
            scefOrmSession.rollback();
        } finally {
            scefOrmSession.endTransaction();
        }

  

有几个key:

1)调试

set tx_isolation = 'read-uncommitted';

select @@tx_isolation;  (相关联的文章:mysql 隔离级别 幻读 测试

隔离级别的设定与连接有关,与是否开启事务 autocommit false无关autocommit true,也有事务隔离级别,只不过是对于single sql而言;默认REPEATABLE-READ

 

2)开启事务

con.setAutocommit(false);

本次,【为什么select不参与】select的连接不参与threadlocal,用单独的connection,因为代码改动量大,而且没必要——即使不开启事务,仅设置隔离级别,也能达到事务隔离级别的效果——因为隔离级别仅与连接connection有关,与是否开启事务 autocommit false无关

比如

function {

con1.setAutocommit(false);

con2.setisolation(read-uncommited);(jdbc有这个函数)

con1.insert();

con2.select(); 可以得到未被提交的insert数据

con1.commit(); 

}

【反转】几天后,select也加入事务,因为此前忽视了:

-同一个函数(或一组函数组成的请求)启用事务,本事务insert/update/delete的已提交或未提交的结果应该对本事务所有select无条件可见,其他事务已提交未提交的结果对本事务的可见效应视本事务con的隔离级别而定,因此他们最好事务内共用一个connection;如果单独如上设置隔离级别,确实可能事务内其他连接的select也可以达到效果,但是你得每个都这样设置ru,其他selec又要rc,影响代码封装性;而且这个con2(ru)读取了同函数的con1未提及的数据同时,也连带读取了其他事务的未提交数据,影响了隔离的灵活性

-事务还有个功能被忽略了,即for update;先select for update,获取锁,该锁只有本事务(连接)的sql可以操作数据,假如后面是个update,用了其他连接,则阻塞,自己阻塞自己,死锁;故要将select也纳入threadlocal《connection》考虑范围,只读事务与普通读  一种mysql jvm死锁

-上面的伪代码,事务内多次其它连接的select无法应对事务内rr级不可重复读要求,因为这些select不在connection内,也就不在事务内,前后2次select mysql的事务号不同,看到的数据会不同,因此select必须启用common connection只读事务与普通读  ,共用connection是同一个事务(同时享受事务除回滚外的其它功能,如锁,rr)的充分非必要条件

 

3)我们用threadlocal,对于tomcat nio是否可用?

nio,如果在threadLocal中放channel相关的东西,比如response,肯定是不行的,一个线程服务多个channel,因此才有netty channel.attr,与channel绑定的threadLocal,netty(六)WebSocket实践

但我们在里面放channel无关的东西,请看4)

*****************

那么bio呢?比如,放用户信息,只有长连接可以,比如用户发起连接-服务端分配一个线程-发起http登陆请求-同一个连接发起http数据请求-服务端同一个线程校验threadlocal,这个过程是可以的

但如果是短链接,用户发起连接-服务端分配一个线程-发起http登陆请求-关闭连接-另一个连接发起http数据请求-服务端另一个线程校验threadlocal,找不到,因为线程变了

nio就更是了,用户1发起连接-服务端分配一个线程-用户1发起http登陆请求-用户2发起连接-服务端分配同一个线程-用户2发起http数据请求-服务端在该线程下threadlocal找到了用户1的信息,完了

所以登陆信息一般放在httpheader里面,做成无状态的,session就是这样做的

*****************

 

4)一个线程会不会运行到一半去运行其他代码?这也就是我们在tomcat nio环境下使用threadlocal的顾虑,因为一个线程不停的被调度去执行许多channel的工作

代码1          代码2

set autocommit false

sth            

             commit

commit(报错)

为什么会有这个疑问呢?

因为nio的同一个线程就被调度去处理多个channel,一直给我一种感觉,线程一个channel read到一半会被调度去处理另一个channel,实际上是不会的

应该是这种模式,select - 发现有可读 -threadpool.submit( new Runnable (channel.channelhandler.read)),当然一个channel自始至终绑定一个线程,但始终是一个一个read挨着执行的,参考从实践模拟角度再议bio nio【重点】得出此结论

所以我们始终能够在一个线程内保持 set autocommit false ---- commit/rollback的连续执行,换言之,多个这个过程的代码在同一个线程中执行周期不重合比如这样的场景,一个http请求,拦截器里获取userid,放入threadlocal,service层get threadlocal,这个过程是连续的,中间,即使是nio,该线程不会被调度到其他连接去,除非代码非要这样写,channel1.read-channel1.controller.set-channel2.read-channel2.controller.set-channel1.service.get error,一般我们用netty不会也没机会这样写

有点像spring的session

 

 

5)在orm join第3)点中,缓存用的是成员变量ConcurrentHashMap,并发时多线程之间清缓存会干扰,在本次第3)4)点的思考基础上,改用threadLocal<HashMap>

private static final ThreadLocal<Map<String, Object>> mapCacheThreadLocal = new ThreadLocal<>();

    public void clearCache() {
        if(mapCacheThreadLocal.get() != null) {
            mapCacheThreadLocal.get().clear();
            mapCacheThreadLocal.remove();
        }
    }

                if(mapCacheThreadLocal.get() == null)
                    mapCacheThreadLocal.set(new HashMap<>());
                Object cache = mapCacheThreadLocal.get().putIfAbsent(selectSql.toString(), obj);

  

threadLocal对象本身不需要考虑线程安全,注意remove

threadLocal对应的value,如果是common的对象,需要考虑线程安全,比如,set了一个公共对象,每个线程虽然借用threadlocal,但搞了半天还是操作同一个对象

 

6)经过oracle事务实践可行,oracle也是用autocommit来控制事务

 

2020.4.13 时区

时区 1-8

 

2020.4.22 非spring环境的junit

架设testcase体系,对数据源在运行环境以及testcase进行切面并不同处理,对业务层无感

 

2020.4.23 日志统一

类加载器隔离朴实案例(二)logback 9~11

参考:从源码来理解slf4j的绑定,以及logback对配置文件的加载

 

2020.4.24 oracle回写主键

                jdbcPool.returnObject(jdbcUtil);
        }

        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            SCEF_DB_FIELD_MAIN_ID main_id = field.getAnnotation(SCEF_DB_FIELD_MAIN_ID.class);
            if (main_id != null) {
                try {
                    Class fcl = field.getType();
                    Object objId = null;
                    String strId = String.valueOf(id);
                    if (fcl == Integer.class)
                        objId = Integer.parseInt(strId);
                    else if (fcl == Long.class)
                        objId = Long.parseLong(strId);
                    else if (fcl == BigInteger.class)
                        objId = new BigInteger(strId);
                    else if (fcl == Short.class)
                        objId = Short.parseShort(strId);
                    else if (fcl == String.class)
                        objId = strId;
                    else
                        throw new DBException("unknown type of main field to set sequence into.");

                    field.set(obj, objId);
                } catch (DBException e) {
                    throw e;
                } catch (Exception e) {
                    throw new DBException("Try to set mainKey value to obj error.");
                }
            }
        }
        return String.valueOf(id);

  

2020.4.28

在2020.4.7 原理:事务的原理 学习笔记 中,追加readOnly与隔离级别设定

public void startTransaction() {
startTransaction(false, -1);
}

public void startTransaction(boolean readOnly, int isolation) {
AbstractJdbcUtil util = null;
try {
util = getJdbcPool().borrowObject();
utilThreadLocal.set(util);
Connection connection = util.getConnection();
connection.setAutoCommit(false);
if(readOnly)
connection.setReadOnly(true);

if(isolation != -1)
connection.setTransactionIsolation(isolation);

} catch (Exception e) {
throw new DBException("start transaction error");
}
}

 只读事务与普通读 中有对只读事务不能执行insert的实践

 

 

2020.4.28 事务代理切面

在2020.4.7 原理:事务的原理 学习笔记 中,所有service 的db操作都得 try commit catch rollback finally close,代码繁琐,模仿spring做一个aop

同时使用noRollBackFor

包扫描:Java遍历包中所有类方法注解 

类增强:jdk动态代理源码底层(jdk生成字节码及5种字节码生产方式比较) 

注入:结合自定义注解的 spring 动态注入 or guice

事务模型:本文2020.4.7

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SCEF_DB_TRANSACTIONAL {
    Class<? extends Throwable>[] noRollbackFor() default {};
    boolean readOnly() default false;
    int isolation() default -1;
}

  *参考:org.springframework.transaction.annotation.Transactional

 

package com.example.demo.testcase.orm.transactionaop;

import com.example.demo.testcase.DBException;
import com.example.demo.testcase.orm.ScefOrmSession;
import com.example.demo.util.SpringContextUtil;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * Created by joyce on 2020/4/28.
 */
public class TransactionProxyFactory implements InvocationHandler {
    private Object target;
    public TransactionProxyFactory(Object target){
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        SCEF_DB_TRANSACTIONAL scef_db_transactional = method.getAnnotation(SCEF_DB_TRANSACTIONAL.class);
        if(scef_db_transactional != null) {
            ScefOrmSession scefOrmSession = (ScefOrmSession)SpringContextUtil.getBean(ScefOrmSession.class);
            Boolean readOnly = scef_db_transactional.readOnly();
int isolation = scef_db_transactional.isolation(); try { scefOrmSession.startTransaction(readOnly, isolation); Object returnValue = method.invoke(target, args); scefOrmSession.commit(); return returnValue; } catch (InvocationTargetException ie) { Throwable throwable = ie.getTargetException(); Class c1 = throwable.getClass(); Class [] c2 = scef_db_transactional.noRollbackFor(); int sum = 0; for(Class c : c2) { if(c.equals(c1)) sum ++ ; } if(sum == 0) scefOrmSession.rollback(); else scefOrmSession.commit(); throw new DBException(throwable); } catch (Exception e) { throw new DBException(e); } finally { scefOrmSession.endTransaction(); } } else { Object returnValue = method.invoke(target, args); return returnValue; } } //给目标对象生成代理对象 public Object getProxyInstance(){ return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } }

 5.14补充:

            //    throw new DBException(throwable);
                // 抛原始异常,原汁原味
                throw throwable;
            } catch (Exception e) {
            //    throw new DBException(e);
                throw e;
            } finally {

 6.26:封装异常处理之坑

Orm2Service orm2Service = new Orm2ServiceImpl();
Orm2Service proxy = (Orm2Service) new TransactionProxyFactory(orm2Service).getProxyInstance();
proxy.xxxx();

 (注入proxy)

包扫描:Java遍历包中所有类方法注解 由于使用guice注入,不使用spring批量注入,故也不需要这个功能

注入:结合自定义注解的 spring 动态注入 or guice ,使用guice单个注入,不需要代码动态批量注入

类增强:jdk动态代理源码底层(jdk生成字节码及5种字节码生产方式比较) done,3个坑

jdk动态代理,必须在interface上加自定义注解,否则被增强类没有自定义注解,为什么?可看 当自定义注解遇到spring和类增强

2 jdk动态代理与ioc 当动态代理遇到ioc   

根本原因:动态代理和cglib,会丢掉被代理类成员变量和方法上的注解

1)spring注入时增强: 使用@Bean(spring 循环依赖,构造函数注入与setter注入(属性注入)有什么优劣,三种注入方式又有什么优劣)

    /**
     * https://www.cnblogs.com/silyvin/p/11900981.html
     * https://www.cnblogs.com/silyvin/p/12803333.html
     * @return
     */
	@Bean
	Orm2Service orm2Service() {
		Orm2Service orm2Service = new Orm2ServiceImpl();
		Orm2Service proxy = (Orm2Service) new TransactionProxyFactory(orm2Service).getProxyInstance();
		return proxy;
	}

 2)Controller依赖被代理类

@Controller
@RequestMapping("/orm2")
public class Orm2Controller {

    @Autowired
    private Orm2Service orm2Service;

 3)被代理类依赖其它bean

public class Orm2ServiceImpl implements Orm2Service {

    @Autowired
    private Orm2Controller orm2Controller;【重点,看此是否装配 非null】

  

1)guice注入时增强:

    bind(Service.class).toProvider(new TransactionProxyProvider<Service>(new ServiceImpl())).in(Singleton.class);
////////////////////////////////////
    private static final class TransactionProxyProvider<T> implements Provider<T> {

        private Object target;

        public TransactionProxyProvider(Object target) {
            this.target = target;
        }

        @Override
        public T get() {
            try {
//                Class cl = target.getClass();
//                Field [] fields = cl.getDeclaredFields();
//                for(Field field : fields) {
//                    field.setAccessible(true);
//                    if(field.isAnnotationPresent(Inject.class))
//                        throw new RuntimeException("proxy class do not allow com.google.inject annotation - " + cl.getName());
//                }
                return (T)new TransactionProxyFactory(target).getProxyInstance();
            } catch (Exception e) {
                loggerCommon.error(e.getMessage(), e);
            }
            return null;
        }
    }

  

2)其它环境类依赖被代理类

@Inject
private Service service;

  

3)被代理类依赖其它bean

@Inject
private Dao dao;

@Inject
private ScefOrmSession scefOrmSession

  

 

3 最终我这样的处理仍然不能实现事务的传播,意味着调用链上仅允许一个方法被增强,比如service func1被增强,dao func2不能了

以2020.4.7的代码,有以下可能

3.1 service事务,dao不事务

service dao

borrow con1

threadlocal.set

 
do sql  
  threadlocal.get
  do sql

threadlocal.remove

return con1

 

 

可以运行,事务传播了,事务也可回滚

service层调用dao前con:1164562078
dao层con:1164562078
service层调用dao后con:1164562078

 

3.2 service事务,dao事务

service dao

borrow con1

threadlocal.set

 
do sql  
 

borrow con2 dao与servcie割裂了con,无法享受 2020.4.7 2)中的3点内容,但不算error,spring也有另起事务的操作

threadlocal.set 覆盖了con1

  do sql
 

threadlocal.remove

return con2

 do sql

threadlocal.get == null

borrow con3 error1,变成非事务了,单独con(autocommit true),error2失去事务回滚

return con3

 
error3,return con1失败,因为没了,连接泄漏,而且还是一个autocommit为false且没有commit的连接  

 3个error

service层调用dao前con:330248030
driver:com.mysql.jdbc.Driver@5c1727d5:com.example.demo.testcase.FakeJdbcDriverClassLoader@7997b197
dao层con:1042812021
service层调用dao后con:0

 

4 注意,经实践,被增强的类,如果自己调用自己,不会又被代理,不会出现第3点的问题

因为this.xxx调用的的service未被代理也未被注入的对象,有点像spring自己调用自己的aop问题:spring aop 内部方法与aspectJ

 

 

 

2020.5.5 一次事故,关闭事务归还连接忘记autocommit归位导致连接没有自动提交

代理service 之后其他非代理(无需事务)操作

borrow con1

set autocommit false

or set readOnly true

threadlocal.set

 

threadlocal.remove

return con1

 
退出代理  
 

borrow con1

延续了autocommit false

or readOnly true

 

con1.update

db始终没有显示更新,因为没有commit

return con1

(此处可以是其他代理service)

borrow con1

set autocommit false

or set readOnly true

threadlocal.set

do sth sql

 

con1.commit(本例是刷新浏览器发出一次新的请求)后db终于显示更新

threadlocal.remove

return con1

 

本质是由于开启事务时,对con有个性设置,关闭事务时没有归位,而又是连接池所致

只是太巧了,3次borrow都是同一个con1,问题的出现具有随机性

改正:

private void endTransactionReal() {
if(utilThreadLocal.get() != null) {

// 归位
try {
utilThreadLocal.get().getConnection().setAutoCommit(true);
utilThreadLocal.get().getConnection().setReadOnly(false);
} catch (SQLException e) {
e.printStackTrace();
}
getJdbcPool().returnObject(utilThreadLocal.get());
utilThreadLocal.remove();
}
}

 

 

2020.5.5  事务的传播

为了解决2020.4.28 3.2的3个问题

采用引用计数算法

保证嵌套注解的事务,或外层service@Transaction注解,内层dao无注解的代码,始终使用一个connection,经过一次startTransactionReal和一次endTransactionReal

效果相当于spring的PROPAGATION_REQUIRED  如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。

 

private static final ThreadLocal<Integer> hasInTransaction = new ThreadLocal<>();

 

    public void startTransaction(boolean readOnly, int isolation) {

        // 起初的代码 dfsljdfls 导致嵌套问题
        //startTransactionReal(readOnly, isolation);
        if(hasInTransaction.get() == null || hasInTransaction.get() == 0) {
            startTransactionReal(readOnly, isolation);
            hasInTransaction.set(1);
        } else {
            hasInTransaction.set(hasInTransaction.get() + 1);
        }
    }

    // dfsljdfls 断点,该函数在事务嵌套时只能调用一次
    private void startTransactionReal(boolean readOnly, int isolation) {
        AbstractJdbcUtil util = null;
        try {
            util = getJdbcPool().borrowObject();
            utilThreadLocal.set(util);//重要,即使后面两句报错,也能在finally中的endTransaction将连接还给连接池,不造成泄漏
            Connection connection = util.getConnection();
            connection.setAutoCommit(false);
            if(readOnly)
                connection.setReadOnly(true);

            if(isolation != -1)
                connection.setTransactionIsolation(isolation);

        } catch (Exception e) {
            throw new DBException("start transaction error");
        }
    }

    public void endTransaction() {
        // 起初的代码 dfsljdfls 导致嵌套问题
        //endTransactionReal();
        hasInTransaction.set(hasInTransaction.get() - 1);
        if(hasInTransaction.get() == 0) {
            endTransactionReal();
            hasInTransaction.remove();
        }
    }

    // dfsljdfls 断点,该函数在事务嵌套时只能调用一次
    private void endTransactionReal() {
        if(utilThreadLocal.get() != null) {

            // 归位
            try {
                utilThreadLocal.get().getConnection().setAutoCommit(true);
                utilThreadLocal.get().getConnection().setReadOnly(false);
            } catch (SQLException e) {
                e.printStackTrace();
            }
            getJdbcPool().returnObject(utilThreadLocal.get());
            utilThreadLocal.remove();
        }
    }

 

service层调用dao前con:1952079634
dao层con:1952079634
service层调用dao后con:1952079634

 

public int getThreadLocalUtilAddressHash() {
return System.identityHashCode(utilThreadLocal.get());
}
 

proxy service

proxy dao

proxy service

non proxy dao

non proxy service

proxy dao

non proxy service

non proxy dao

before dao     =0 =0 
dao     !=0  =0
after dao     =0   =0
条件  before=dao=after>0   before=dao=after>0

 before=after=0

dao !=0

 before=dao=after=0

 

 

然而忽略了commit和rollback

commit:

service dao
try  
start 事务  
sql1  
  try
  start事务
  sql2
  commit sql1 sql2
  finally end事务
 sql3  
commit sql3  
finnally end事务  
   
   

看到:虽然不会有毁灭性的后果,sql1 sql2 、sql3被分开commit了,不太严谨

service dao
try  
start 事务  
sql1  
  try
  start事务
  sql2
  commit sql1 sql2
  finally end事务
 sql3  

catch e

rollback only sql3

throw e

 
finnally end事务  
   
   

这个例子就没那么幸运了,2个sql被提前commit了,没法rollback

 

rollback

service dao
try  
start 事务  
sql1  
  try
  start事务
  sql2
  catch
  sql1 sql2 rollback
 catch throw e
empty rollback finally end事务
throw e  
finally end事务  
   

 

看到:无什么影响,就是多了一个空的rollback,但考虑代码的工整对称,仍然处理

    public void commit() {
        try {
            // 保证只有最外层的代理统一commit
            if(hasInTransaction.get() == 1)
                utilThreadLocal.get().getConnection().commit();
        } catch (Exception e) {
            throw new DBException("transaction commit error");
        }
    }

    public void rollback() {
        try {
            // 保证只有最外层的代理统一rollback
            if(hasInTransaction.get() == 1)
                utilThreadLocal.get().getConnection().rollback();
        } catch (Exception e) {
            throw new DBException("transaction rollback error");
        }
    }

 

 

2020.5.13 optimistic lock

hibernate中对应的乐观锁异常:

Exception in thread "main" org.hibernate.StaleObjectStateException:Row was updated or deleted by another transaction (or unsaved-value mapping wasincorrect): [Version.Student#4028818316cd6b460116cd6b50830001]

我们也实现一个

                if(domain.getVersion()) {
                    updateSql.append(" ").append(fieldName).append("=").append(fieldName).append("+1");
                    StringBuilder vApp = new StringBuilder(" and ").append(fieldName).append("=").append(fieldValue);
                    version = vApp.toString();
                    continue;
                }

  

StringBuilder sql = new StringBuilder("update ").append(tableName).append(" set ").append(updateSql)
                    .append(" where ").append(mainFieldName).append("=").append("'").append(mainFieldValue).append("' ").append(version);

  

 int ret = preparedStatement.executeUpdate();
            preparedStatement.close();
            //    jdbcUtil.executeNonQuery(sql.toString());

            if(ret == 0 && !"".equals(version))
                throw new ScefOptimisticLockException("table " + tableName + " version " + version);
        } catch (ScefOptimisticLockException e) {
            throw e;
        } catch (Exception e) {
            throw new DBException(e);

  

 

测试用例:

1 查询后更新,应显示version+1,顺利更新

2 构建一个没有@Version 的bean,更新id=2,无法命中,ret=0,但是理应不会触发乐观锁异常

3 查询后更新,期间手动改掉db的version,应出发乐观锁异常

    @RequestMapping(value = "/lockRet1")
    @ResponseBody
    public TestMyLock lockRet1() {
        TestMyLock testMyLock = new TestMyLock();
        testMyLock.setId(1);
        scefOrmSession.queryForObject(testMyLock);

        try {
            testMyLock.setNa("modified");
            scefOrmSession.update(testMyLock);
            System.out.println("顺利更新");
        } catch (ScefOptimisticLockException e) {
            System.out.println("乐观锁异常");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return testMyLock;
    }

    @RequestMapping(value = "/lockRet0Success")
    @ResponseBody
    public TestMyLockNonLock lockRet0Success() {
        TestMyLockNonLock testMyLock = new TestMyLockNonLock();
        testMyLock.setId(1);
        scefOrmSession.queryForObject(testMyLock);

        try {
            testMyLock.setId(2);
            scefOrmSession.update(testMyLock);
            System.out.println("顺利更新");
        } catch (ScefOptimisticLockException e) {
            System.out.println("乐观锁异常");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return testMyLock;
    }

    @RequestMapping(value = "/lockRet0Exception")
    @ResponseBody
    public TestMyLock lockRet0Exception() {
        TestMyLock testMyLock = new TestMyLock();
        testMyLock.setId(1);
        scefOrmSession.queryForObject(testMyLock);

        try {
            // 手动version改变
            Thread.sleep(8000);
            testMyLock.setNa("modified");
            scefOrmSession.update(testMyLock);
            System.out.println("顺利更新");
        } catch (ScefOptimisticLockException e) {
            System.out.println("乐观锁异常");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return testMyLock;
    }

 

当结合while使用时,务必确保:

1)autocommit true

2)autocommit false,隔离级别非默认的rr

否则陷入mysql rr级别可重复读的无限循环中,jdk与mysql的cas 

 

 

2020.5.19

queryforobject暴露给外部,统一进行clearCache

queryforobjectCache给循环依赖

    public <T> T queryForObject(T obj) {

        clearCache();
        return queryForObjectCache(obj);
    }

    protected  <T> T queryForObjectCache(T obj) {

 

 

2020.5.21 多数据源,生产为sybase+oracle,我们本地用mysql5+mysql8模拟

我们此前的设计已经考虑多数据源,本次实践,并发现一些细节

0 测试代码:

mysql 5 query

mysql 8 query

 

1 mysql connector 8 url要求:

jdbc.oracle.url=jdbc:mysql://127.0.0.1:33306/mytest?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai

时区变成了一个必要的参数

 

2 mysql connector 5和8有类冲突

                    /**
                    * 必须把8放在前面,否则导致加载com.mysql.jdbc.Driver时把com.mysql.cj.jdbc.Driver注册进去了
                    */new JarInputStream(inputStream),
                    new JarInputStream(inputStreamMysql8),

 

把8放在后面——hashmap<name, byte[]> 8 的com.mysql.jdbc.driver覆盖了5的,我们看看8的定义:

package com.mysql.jdbc;

import java.sql.SQLException;

public class Driver extends com.mysql.cj.jdbc.Driver {
    public Driver() throws SQLException {
    }

    static {
        System.err.println("Loading class `com.mysql.jdbc.Driver\'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver\'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.");
    }
}

 

package com.mysql.cj.jdbc;

import com.mysql.cj.jdbc.NonRegisteringDriver;
import java.sql.DriverManager;
import java.sql.SQLException;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can\'t register driver!");
        }
    }
}

 

结果就是继承了com.mysql.cj.jdbc.Driver,注册时把8的注册进去了,导致第一步 mysql 5 query时,用的是driver 8,它问我们要必要的时区,而这在5中是不必要的,导致出错

mysql 5 query - 加载com.mysql.jdbc.Driver - 加载com.mysql.cj.jdbc.Driver - 注册com.mysql.cj.jdbc.Driver - Malformed database URL, failed to parse the connection string near ';characterEncoding=utf-8&useSSL=false'

 

3 既然mysql8的url要求有时区信息,而我们的orm有一套自己的时区处理,岂不是会错乱

经过实践

#url设置为UTC(db)会导致查询时orm TimezoneController返回错误,因为经过2次转换,相当于+16时区insert由于是手动转换字符串不受影响
#url应设置为+8,与服务器一致,免去jdbc自行根据url的自动时区处理
#jdbc.sybase.url=jdbc:mysql://127.0.0.1:53306/mytest?useUnicode=true&amp;characterEncoding=utf-8&useSSL=false&useTimezone=true&serverTimezone=UTC
jdbc.sybase.url=jdbc:mysql://127.0.0.1:53306/mytest?useUnicode=true&amp;characterEncoding=utf-8&useSSL=false&useTimezone=true&serverTimezone=Asia/Shanghai
#jdbc.sybase.url=jdbc:mysql://127.0.0.1:53306/mytest?useUnicode=true&amp;characterEncoding=utf-8&useSSL=false

故我们的url不能用db时区UPC,应该用服务器相同时区+8,免去jdbc自行根据url的自动时区处理

时区 9

 

4 生产环境sybase+oracle实践成功

 

5 目前的多数据源代码不支持各自时区,具体看时区 11

 

2020.5.28

修复TimeZone.getDefault()的java bug,具体看 时区 10

 

2020.6.2

2020.4.28 事务代理切面 + 2020.5.21 多数据源 产生问题:(第二数据源dao的调用链任意一层方法上有事务注解)

service

主数据源

dao

次数据源

有事务注解 无事务注解

开启事务

threadlocal<Con>.set

 
dao.xxx query
 

con=threadlocal.get

if(con==null) borrow one

  con.xxx
  找不到表
   

解决方案:

1)去除调用链上方法所有事务注解,有局限性

2)第2数据源定制非事务版本query,直接borrow,绕过threadlocal

/**
 * 第2个数据源
 * Created by joyce on 2020/5/18.
 */
public class PropertiesOracleOrmSession extends ScefJoinOrmSession {
    @Override
    protected GenericObjectPool<AbstractJdbcUtil> getJdbcPool() {
        return SpringContextHolder.getBean("oracleJdbcNativePool");
    }

    public List<Map<String, Object>> queryNonTransactional(String sql) {
        AbstractJdbcUtil jdbcUtil = null;
        GenericObjectPool<AbstractJdbcUtil> jdbcPool = null;
        List list = null;
        try {
            jdbcPool = getJdbcPool();
            jdbcUtil = jdbcPool.borrowObject();
            list = jdbcUtil.queryForList(sql);
        } catch (Exception e) {
            throw new DBException(e);
        }
        return list;
    }
}

 

 

2020.6.10

此前使用getColumnName

oracle jconn2-5.5 select as 生效了,h2 1.2.199没生效

区别:getColumnName可能只能取到查询的数据库表的字段名称,而不是sql语句中用到的别名,而getColumnLabel取到的是sql语句中指定的名称(字段名或别名)

mysql在用两个方法获取sql语句名称时显然getColumnName不符合使用者的要求,取到的不是别名。但是oracle对于两种方法取到的值是一样的。因此一般情况下还是建议使用getColumnLabel方法

实测,mysql connector8,getColumnName返回原始fieldName,getColumnLabel返回alias

h2同样

 

 

 

2020.6.20

双数据源时区,具体看 时区 12

 

 

 

2020.6.24

第二数据源启用事务支持

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SCEF_DB_TRANSACTIONAL {
    Class<? extends Throwable>[] noRollbackFor() default {};
    Class<? extends ScefOrmSession> sessionProvider() default PropertiesSybaseOrmSession.class;
    boolean readOnly() default false;
    int isolation() default -1;
}
SCEF_DB_TRANSACTIONAL scef_db_transactional = method.getAnnotation(SCEF_DB_TRANSACTIONAL.class);
        if(scef_db_transactional != null) {

            Class cSession = scef_db_transactional.sessionProvider();
            ScefOrmSession scefOrmSession = (ScefOrmSession)getBeanFromFactorySpring(cSession);

            if(scefOrmSession == null)
                throw new RuntimeException("no session found - " + cSession.getName());

        //    ScefOrmSession scefOrmSession = getBeanFromFactorySpring(ScefOrmSession.class);
            Boolean readOnly = scef_db_transactional.readOnly();
            int isolation = scef_db_transactional.isolation();
            try {

 该法,仅支持以第二数据源开启事务,调用链上仅一个事务管理器,不同于mybatis Guice 事务源码解析支持调用链多数据源多事务管理器

 

 2020.6.24

多数据源session继承关系梳理

 

2020.6.24

aop对于被代理类中没有被事务注解修饰的方法,异常修复

本质是因为,对于被代理类反射调用方法,抛出的异常均应取出原异常,在else中遗漏了,故把反射异常抛上去了

根本原因:动态代理和cglib,会丢掉被代理类成员变量和方法上的注解

 

 

 

2020.7.13 

由于2020.3.20 orm join 中第3)点,因为:

1)图方便将入参obj0代替obj1,作为循环以来预缓存

2)而clone 时将db里查出来的obj3 clone to obj0,obj3是没有NO_FIELD字段value的,导致obj3的null覆盖了入参obj0的非null NO_FIELD字段,入参的非null NO_FILED字段被干掉了

 

 

 

 

2020.7.17

库存问题锁的思考

 

    public boolean saveRecord(CEFItem newItem, CEFItem oldItem, String soeId) throws SCEFApplicationException {
        logger.info("saveRecord():Begin");
        synchronized (CEFUtils.TABLE_LOCKER.get(newItem.getClass().getSimpleName())) {
            try {

。。。。。。

bind(CefItemService.class).toProvider(new TransactionProxyProvider<CefItemService>(new CefItemServiceImpl())).in(Singleton.class);


    private static final class TransactionProxyProvider<T> implements Provider<T> {

        private Object target;

        public TransactionProxyProvider(Object target) {
            this.target = target;
        }

        @Override
        public T get() {
            try {
//                Class cl = target.getClass();
//                Field [] fields = cl.getDeclaredFields();
//                for(Field field : fields) {
//                    field.setAccessible(true);
//                    if(field.isAnnotationPresent(Inject.class))
//                        throw new RuntimeException("proxy class do not allow com.google.inject annotation - " + cl.getName());
//                }
                return (T)new TransactionProxyFactory(target).getProxyInstance();
            } catch (Exception e) {
                loggerCommon.error(e.getMessage(), e);
            }
            return null;
        }
    }

  

这种事务代理+synchronzed+rc及以上隔离级别 会造成

线程1 线程2
start transaction 1 start transaction 2
synchronized synchronized
取得锁  

db 查询 1

+1=2

update 2

 
让出锁  取得锁
 

 db 查询 1(rc及以上隔离级别无法看到其他session未提交的数据)

+1

udpate 2

 提交事务  提交事务

 

 

2020.7.25 迁移方案一

要求改为mybatis,想出如下方案:

 

 

 

 

2020.7.30 $$27

mybatis处理时区时,涉及到 mapper拦截器,common包只允许select注解方法拦截,不不拦截update和insert

使用javassist运行期加载前篡改common中该类字节码 javassist 运行期改类

 

 

 

2020.7.31 迁移方案二 $$26

迁移Hibernate-》MyBatis,有以下关键点和风险点

多数据源框架(3个物理源,4个逻辑源,考虑到2个schema需要共同事务)

sequence

时区

二进制

testcase环境无感

联合 One Many

事务

乐观锁

 

观察到MyBatis的@Result注解中,除了one,many,还有一个typeHandler,灵活性极强,解决所有Hibernate-》MyBatis的迁移存在的关键技术痛点

1)typeHandler凭借一己之力,提供比hibernate、myorm强的多的高机动性orm,双向既共性又个性的拦截参数;居然顺便还能解决掉时区的问题,免去javassist暴力拦截器

2)老程序员不施力于代码堆积式编写,集中力量搭建框架、提供高效解决关键技术复杂点和风险点的方案,锁定切入点,最少而集中的代码改动,极大提升未来3周团队的生产力,极大的降低迁移风险

参考:https://www.cnblogs.com/lenve/p/10661934.html

mybatis orm解决方案

2020.8.7 done

 

 

 

$$28

2020.8.1 动态代理究竟会不会遗失被代理类field和method上的注解

2020.4.28 事务代理切面中,提出:

1)事务注解要加到接口

2)注入动态代理类后,被代理类的@Autowired @Inject装配失效

 

做个实验:

TransactionProxyFactory
   @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        { //$$28
            // 代理对象,只有一个成员变量,就是TransactionProxyFactory对象
            Class proxyClass = proxy.getClass();
            Field[] proxyFields = proxyClass.getDeclaredFields();
            for (Field field : proxyFields) {
                field.setAccessible(true);
                if (field.getType().equals(Method.class))
                    continue;
                SCEF_DB_TRANSACTIONAL proxyTransaction = field.getAnnotation(SCEF_DB_TRANSACTIONAL.class);
                System.out.println("代理类成员是否有事务注解:" + field.getName() + (proxyTransaction != null));
            }

            // 代理对象,所属类,有无transactional注解,跟着接口来
            Method proxyMethod = proxyClass.getMethod(method.getName(), method.getParameterTypes());
            if ("checkProxyServiceAndProxyDao".equals(method.getName())) {
                SCEF_DB_TRANSACTIONAL proxyTransaction = proxyMethod.getAnnotation(SCEF_DB_TRANSACTIONAL.class);
                System.out.println("代理类方法是否有事务注解:" + method.getName() + (proxyTransaction != null));
                if (proxyTransaction != null) {
                    System.out.println("代理类方法是否有事务注解:" + proxyTransaction.sessionProvider());
                }
            }

            // 被代理的impl对象,可以取到方法上的注解
            Method classMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
            SCEF_DB_TRANSACTIONAL proxyTransaction = classMethod.getAnnotation(SCEF_DB_TRANSACTIONAL.class);
            if ("checkProxyServiceAndProxyDao".equals(method.getName())) {
                System.out.println("target类方法是否有事务注解:" + method.getName() + (proxyTransaction != null));
                if (proxyTransaction != null) {
                    System.out.println("target类方法是否有事务注解:" + proxyTransaction.sessionProvider());
                }
            }
        }

 

 

结论是:

1 动态代理类method上注解,跟着接口的,因为它是用接口造出来的,jdk动态代理源码底层(jdk生成字节码及5种字节码生产方式比较)

2 被代理的impl对象,上面的注解是可以取到的

3 动态代理类里面,根本没有被代理类的成员变量,有无遗失成员上的注解更无从谈起了

是通过InvocationHandler.invoke中,

Object returnValue = method.invoke(target, args);
return returnValue;

通过被代理类对象target,来间接引用被代理类成员的;所以即使这些成员上有装配的注解,没用,因为注入的是代理类,这个被代理类只是维护在InvocationHandler中的一个普通对象,根本不在ioc中,自动装配也就无从谈起了

target原本要注入ioc为其@Autwired和@Inject获得自动装配,然后最终被注入的不是他而是啥成员也没有的代理类,所以才有了后来的对target成员的反哺

4 可以看到

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

中的第一个参数proxy,是代理类对象,也就是以接口名义

(xxService, xxServiceImpl, xxService proxy = (xxService)new TransactionProxyFactory(new xxServiceImpl()).getProxyInstance() )

注入到ioc中的对象

 

5 除了反哺,另一个可能的解决方案:

 

A

autowired B C D;

 

AProxy

A.mehod.invoke {

  A.method() {

    B.xxx();

  }

}

A未注入,AProxy注入,但AProxy调用A方法内,有B.xxx(),B报null,

那么能不能让A也注入,让它参与对B C D的自动装配,注入AProxy,让A可以被其它类自动装配

这个方案在spring中可能会报错,因为A与AProxy类型相同

 

 

 

 

$$29

2020.8.7

mybatis 动态代理 事务 初探

mybatis Guice 事务源码解析

ThreadLocal内存泄漏问题实践(三)非静态threadlocal

mybatis guice 事务代理切面

 

 

 

$$30

2020.8.15 aop代理日志

出入站

logback不同包不同日志

threadlocal解决当前上下文当前用户

deprecated

 

 

 

$$31

2020.8.17

时区,夏令时冬令时发现

时区 16

 

$$32

2020.9.18

时区,夏令时冬令时解决方案 insert/update

时区 17

 

$$33

2020.9.22

时区,夏令时冬令时解决方案 select

时区 17

 

 

$$34

2020.9.25

动态代理事务切面一次IOC

当自定义注解遇到spring和类增强

当动态代理遇到ioc

mybatis guice 事务代理切面

public class TransactionProxyFactory implements InvocationHandler {
    private Object target;
    private volatile boolean alreadyIOC = false;

@Override    
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(!alreadyIOC) { synchronized (this) { if(!alreadyIOC) { System.out.println("首次处理 " + target.getClass() + "的ioc"); Class clProxy = target.getClass(); Field[] fields = clProxy.getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); if (!field.isAnnotationPresent(getBeanInjectAnnotationSpring())) continue; String name = field.getName(); Class fieldType = field.getType(); Object obj = getBeanFromFactorySpring(fieldType); if (obj != null) { field.set(target, obj); System.out.println("处理 " + target.getClass() + "的" + field.getName()); } } alreadyIOC = true; } } }

 

 

 

 

$$35

2020.10.8

接11,结合自定义注解的事务动态代理切面(cglib)

java 的三种代理模式 (二)——子函数切面

cglib与mock

jdk动态代理与cglib优势劣势以及jdk动态代理为什么要interface (二)

当动态代理遇到ioc (二)cglib

 

 

$$36

2020.10.9

静态资源基础数据加载 类加载的并发,单例模式,静态资源加载

 

 

$$37

2020.10.20

夏令时冬令时解决方案 select 修复,完美解决收官

时区 17 (6)

 

 

$$38

2020.10.29 运行期修改数据源mybatis guice 事务代理切面

 

#

2020.12.16 主导抽象ceftable重构

 

$$39

2021.1.5 cglib与asm冲突解决 当动态代理遇到ioc (三)cglib与asm jar包冲突

 

$$40 

2020.1.5 

接$$35,结合自定义注解的事务动态代理切面(cglib切子函数)当动态代理遇到ioc (四)真正的cglib

 

$$41

2020.1.7

代理侵入框架与guice  

 


$$46 2021.7.27 timecut/cache

注意,只在cglib中加入,因为jdk不能切子方法意义不大,此部分代码不进仓库

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

        IOC();

        SCEF_DB_TRANSACTIONAL scef_db_transactional = method.getAnnotation(SCEF_DB_TRANSACTIONAL.class);
        Class[] interfaces = target.getClass().getInterfaces();
        if (scef_db_transactional == null && interfaces != null && interfaces.length > 0) {
            try {
                Method classMethod = interfaces[0].getMethod(method.getName(), method.getParameterTypes());
                scef_db_transactional = classMethod.getAnnotation(SCEF_DB_TRANSACTIONAL.class);
            } catch (NoSuchMethodException noMethod) {
                ;
            } catch (Throwable e) {
                logger.error(e.getMessage(), e);
            }
        }



【【【以下有修改】】】 SCEF_DB_CACHE scef_db_cache = method.getAnnotation(SCEF_DB_CACHE.class); Object res = getCache(scef_db_cache, objects, method); long st = new Date().getTime(); try { if(res != null) return res; if (scef_db_transactional != null) { res = invokeHasTransactional(scef_db_transactional, null, objects, methodProxy, o); } else { res = invokeNoTransactional(null, objects, methodProxy, o); } } finally { long ed = new Date().getTime(); double x = ((double)(ed-st)) / 1000; printTime(method, x); } setCache(scef_db_cache, objects, method, res); return res; }

  这里用了try fanally模型finally throw return , 使用threadlocal时也应使用这种模型,确保threadlocal被remove ThreadLocal内存泄漏问题实践(二)

 

private void printTime(Method method, double x) {
        try {
            Injector injector = CRFGuiceContext.getInjector();
            Configuration configuration = injector.getInstance(Configuration.class);
            if (configuration == null) {
                return;
            }

            Boolean print = configuration.getBoolean("SCEF.echomethodtime.enable", false);
            if (!print) {
                return;
            }

            Double cut = configuration.getDouble("SCEF.echomethodtime.cut", 0.1);
            if(x < cut) {
                return;
            }


            logger.info("scefcut {}\t{}\t{}", target.getClass().getSimpleName(), method.getName(), x);

        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }

    private Object getCache(SCEF_DB_CACHE scef_db_cache, Object [] paras, Method method) {
        try {
            if (scef_db_cache == null || scef_db_cache.disabled())
                return null;

            String key = getKey(scef_db_cache, paras, method);
            return cache.get(key);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return null;
        }
    }

    private void setCache(SCEF_DB_CACHE scef_db_cache, Object [] paras, Method method, Object object) {
        try {
            if (scef_db_cache == null || scef_db_cache.disabled())
                return;

            String key = getKey(scef_db_cache, paras, method);
            cache.put(key, object);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }

    private String getKey(SCEF_DB_CACHE scef_db_cache, Object [] paras, Method method) {

        final String SPLIT = ":";
        String key = scef_db_cache.key();
        if(StringUtils.EMPTY.equals(key)) {
            key = new StringBuilder(target.getClass().getSimpleName()).append(SPLIT).append(method.getName()).toString();
        }
        StringBuilder value = new StringBuilder(key).append(SPLIT);
        for(Object para : paras) {
            if(!(para instanceof String))
                continue;

            value.append(para).append(SPLIT);
        }
        return value.toString();
    }

  不支持null cache,null被认为没有获得cache,允许缓存穿透;只有String类型入参参与key的构建

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SCEF_DB_CACHE {
    boolean disabled() default true;
    String key() default StringUtils.EMPTY;
}

  

posted on 2020-06-23 16:06  silyvin  阅读(214)  评论(0编辑  收藏  举报