posted in JavaWeb 

1、简介

前段时间有项目有读写分离的需要,因此完成了该类库mybatis-read-write-split来实现读写分离。

  • 特点 支持两种模式的主备分离:
  • 业务透明的读写分离。自动解析sql的读写类型并进行路由转发。
  • 基于注解的读写分离。通过注解中的配置来进行读写分离。

以上两种模式可以混合使用:缺省自动解析sql的读写类型,如果注解有指定数据源,则根据注解进行路由。

2、用法

  • pom.xml 添加依赖
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-read-write-split-core</artifactId>
            <version>2.0-SNAPSHOT</version>
        </dependency>
  • 配置数据源
    <!--替换原本的DataSource-->
    <bean id="dataSource" class="org.mybatis.rw.MultiReadDataSource">
        <property name="masterDataSource" ref="masterDataSource"/>
        <property name="slaveDataSourceList">
            <list>
                <ref bean="slaveDataSource1"></ref>
                <ref bean="slaveDataSource2"></ref>
            </list>
        </property>
    </bean>
    
    <bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <!--各数据源配置-->
    </bean>
    
    <bean id="slaveDataSource1" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <!--各数据源配置-->
    </bean>
    
    <bean id="slaveDataSource2" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <!--各数据源配置-->
    </bean>

2.1、业务透明区分读写

mybatis自动分析读or写操作,并进行相应的路由操作

  • mybatis配置文件添加interceptor
   <plugins>
        <plugin interceptor="org.mybatis.rw.interceptor.ReadWriteDistinguishInterceptor">
        </plugin>
    </plugins>

2.2、通过注解区分读写

通过方法上的注解显示指定读主库or备库

  • 在目标方法上添加 @DataSource()

    @DataSource(DataSourceType.MASTER)
    public User getUserByIdFromMaster(Integer userId) {
        //some operation
    }

3、内部实现

2.1、业务透明区分读写

  1. Mapper调用MyBatis进行读写
  2. MyBatis分析读写类型,并存入ThreadLocal中
  3. 自定义DataSource从ThreadLocal里获取读写类型,路由给对应的子DataSource
  4. 使用对应的子DataSource进行读写操作

2.2、通过注解实现读写区分

  1. Spring的切面读取注解的内容,分析 读/写 操作
  2. 把分析结果丢到 ThreadLocal中
  3. 自定义DataSource从ThreadLocal里获取DataSource类型,路由给对应的子DataSource
  4. 使用对应的子DataSource进行读写操作

4、项目源码

https://github.com/chenzz/mybatis-read-write-split

posted in JavaWeb 

在进行SOA系统设计,设计微服务的接口形式,会遇到一些问题,

  • 入参是多个入参还是包装在一个对象中
  • 异常返回是通过Exception还是返回码

以下是开发过程中的一些思考与实践:

1、实践

定义成 GetUserInfoResp getUserInfo(GetUserInfoReq req)的形式,

  • 入参

    • GetUserInfoReq继承了AbstractReq
    • AbstractReq保存了公用的请求参数成员变量,如username、ip
  • 出参

    • GetUserInfoResp继承了AbstractResp
    • AbstractResp保存了公用的返回码成员变量,如retCode

代码设计

interface UserProvider {
    GetUserInfoResp getUserInfo(GetUserInfoReq req);
}

class GetUserInfoReq extends AbstractReq {
}

class AbstractReq {
    private String username;
    private String ip;
}

class GetUserInfoResp extends AbstractResp {
    private String school;
    private double weight;
}

class AbstractResp {
    private String retCode;
    private String message;
}

2、入参设计

为什么要把各种成员变量包装在一个Req对象中,而不是做成getUserInfo(String username, String disctrict)的形式呢?

2.1 方便拓展

  • 案例一:

如果这个接口有两个调用方,某一天这个接口新添加了一个入参int gender,其中一个调用方会传这个参数,另一个调用方不传。

如果是当前这个方案,只需要在Req对象中添加一个成员变量int gender即可,然后两个消费方都可以兼容的进行调用。

如果是采用getUserInfo(String username, String disctrict)的方案,那么就只能新加一个方法getUserInfo(String username, String disctrict, int gender)了,造成了代码的冗余。

  • 案例二:

如果某一天,这个系统要支持不同国家的业务,所有的接口都要添加一个参数String nation

那么基于当前的方案,只需要在AbstractReq中添加一个String nation即可,非常方便。

如果基于getUserInfo(String username, String disctrict)的形式,那么可能需要批量修改几百个接口,花费了大量的时间,同时也提高了错误发生的概率。

2.2 方便映射

在SOA的系统设计中,往往会在内部系统的前面添加一个gateway系统来做一些公共逻辑,网关对外提供http服务,而网关和内部系统通过专有协议进行通信,

想想这样一个场景,/user/getUserInfo这个接口提供获取用户信息的服务,用户的请求body可能有以下几种形式:

  • 形式一:
{
    "name": "jason",
    "nation": "america"
}
  • 形式二:
{
    "nation": "america",
    "name": "jason"
}

要把这些形式的请求映射到后端接口上:

2.2.1 如果是当前最佳实践

即如果是getUserInfo(GetUserInfoReq req)的形式

那么网关很好设计,

  1. 提前记录url和接口的映射规则,
  2. 请求来的时候,把url映射成接口,直接把body反序列化成GetUserInfoReq对象即可。
2.2.2 如果是另一种形式

即如果getUserInfo(String name, String nation)的形式

那么网关很不好设计,

  1. 提前记录url和接口的映射规则,

    这个记录会很麻烦,因为需要记录 body中的每个参数和接口中的第几个参数是对应的

  2. 映射也很麻烦,

    需要遍历body中的变量,把body中的某个变量映射成接口第n个参数

究其原因,是因为通过反射可以获取到一个对象的成员变量的名称,却无法获取到一个方法的参数名称。

3、出参设计

为什么通过通过retCode来包装异常情况,而不是直接抛出异常?

3.1 降低系统间的耦合

如果getUserInfo通过异常来表达业务异常情况,那调用方势必要接触到 提供方的各种Exception类型,大大提高了系统的耦合性。

3.2 避免延迟 && 避免占用带宽

Exception会带着一个调用栈,
如果通过网络抛出异常,一方面会占用带宽,一方面会造成延迟。

4、总结

基于以上原因,应该采用GetUserInfoResp getUserInfo(GetUserInfoReq req)的形式定义接口。

posted in JavaWeb 

一、HelloWorld Demo

一个最简单的MyBatis Demo

1、 代码

源码下载:mybatis-demo
对应DB表:mybatis-demo

public class UserService {
    public User getUserById(Integer userId) {
        SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            return userMapper.getUserById(userId);
        } finally {
            sqlSession.close();
        }
    }
}
public class MyBatisUtil
{
    private static SqlSessionFactory factory;

    private MyBatisUtil() {
    }

    static
    {
        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader("mybatis-config.xml");
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage());
        }
        factory = new SqlSessionFactoryBuilder().build(reader);
    }

    public static SqlSessionFactory getSqlSessionFactory()
    {
        return factory;
    }
}
  • mybatis-config.xml
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE configuration
        PUBLIC '-//mybatis.org//DTD Config 3.0//EN'
        'http://mybatis.org/dtd/mybatis-3-config.dtd'>
<configuration>
    <properties resource='jdbc.properties'/>
    <typeAliases>
        <typeAlias type='com.sivalabs.mybatisdemo.domain.User' alias='user'></typeAlias>
    </typeAliases>
    <environments default='development'>
        <environment id='development'>
            <transactionManager type='JDBC'/>
            <dataSource type='POOLED'>
                <property name='driver' value='${jdbc.driverClassName}'/>
                <property name='url' value='${jdbc.url}'/>
                <property name='username' value='${jdbc.username}'/>
                <property name='password' value='${jdbc.password}'/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource='com/sivalabs/mybatisdemo/mappers/UserMapper.xml'/>
    </mappers>
</configuration>
  • UserMapper.xml
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE mapper PUBLIC '-//mybatis.org//DTD Mapper 3.0//EN'
        'http://mybatis.org/dtd/mybatis-3-mapper.dtd'>

<mapper namespace='com.sivalabs.mybatisdemo.mappers.UserMapper'>

    <select id='getUserById' parameterType='int' resultType='com.sivalabs.mybatisdemo.domain.User'>
        SELECT
        user_id as userId,
        email_id as emailId ,
        password,
        first_name as firstName,
        last_name as lastName
        FROM USER
        WHERE USER_ID = #{userId}
    </select>
    <!-- Instead of referencing Fully Qualified Class Names we can register Aliases in mybatis-config.xml and use Alias names. -->
    <resultMap type='com.sivalabs.mybatisdemo.domain.User' id='UserResult'>
        <id property='userId' column='user_id'/>
        <result property='emailId' column='email_id'/>
        <result property='password' column='password'/>
        <result property='firstName' column='first_name'/>
        <result property='lastName' column='last_name'/>
    </resultMap>

    <select id='getAllUsers' resultMap='UserResult'>
        SELECT * FROM USER
    </select>

2、代码逻辑

  • 在执行userMapper.getUserById(userId)之前,会先执行2.1和2.1代码
  • 2.3是userMapper.getUserById(userId)真正执行的代码

2.1 解析配置文件(#hello-world-demo-2-1)

        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader("mybatis-config.xml");
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage());
        }
        factory = new SqlSessionFactoryBuilder().build(reader);

该段代码主要把mybatis-config.xmlUserMapper.xml中的各种配置解析到Configuration实例中去。

2.2 构造UserMapper实例

SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  • 通过openSession()来进行实例的初始化,此处可暂时略过。
  • 通过getMapper()方法来获取Mapper实例。

此处getMapper()方法的内部实现是为UserMapper生成一个动态代理,并且返回:

  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

此处的意思为:
MapperProxy这个类的invoke()方法来实现对应的函数调用流程。

2.3 执行对应查询代码

            return userMapper.getUserById(userId);

因为这里的userMapper对象是动态代理对象,所以其实这里执行的是MapperProxy.invoke()方法。
MapperProxy.invoke()方法内容如下:

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

这里生成了一个MapperMethod 对象,并且调用mapperMethod.execute(sqlSession, args)来进行真正的DB查询逻辑。

mapperMethod.execute(sqlSession, args)的内部逻辑如下所示

2.3.1 找到接口对应的sql

DefaultSqlSession#selectList()方法内容如下:

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

根据接口找到对应的sql,然后交由executor执行

2.3.2 执行sql,封装结果

缺省情况下executor是SimpleExecutorSimpleExecutor.doQuery()方法内容如下:

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
  1. 执行sql,获得结果
  2. 把结果转换成接口中声明的类型然后返回

二、Plugin Demo

演示 MyBatis Plugin 功能的 Demo。

1、代码

源码下载:mybatis-demo
对应DB表:mybatis-demo

添加Interceptor类:

@Intercepts({@Signature(type = StatementHandler.class, method = "parameterize", args = {Statement.class})})
public class SQLStatsInterceptor implements Interceptor {

    public Object intercept(Invocation invocation) throws Throwable {

        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = statementHandler.getBoundSql();
        String sql = boundSql.getSql();
        System.out.println("mybatis intercept sql:" + sql);
        return invocation.proceed();
    }

    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    public void setProperties(Properties properties) {
        String dialect = properties.getProperty("dialect");
        System.out.println("mybatis intercept dialect:" + dialect);
    }
}

添加interceptor配置:

mybatis-config.xml 添加以下配置,

    <plugins>
        <plugin interceptor="com.sivalabs.mybatisdemo.interceptor.SQLStatsInterceptor">
            <property name="dialect" value="mysql"/>
        </plugin>
    </plugins>

2、代码逻辑

2.1 解析配置文件

同上。2.1 解析配置文件

2.2 构造UserMapper实例

SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
2.2.1、通过openSession()来进行实例的初始化。

openSession()的内部内部实现如下:

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

主要业务逻辑为

  1. 开启一个事务
  2. 构造一个newExecutor

configuration.newExecutor()的内部实现如下:

    executor = (Executor) interceptorChain.pluginAll(executor);

把所有的插件(Interceptor)嵌入到Executor中,具体实现如下:

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
  public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

细节已经展现,MyBatis 内部通过 Java 的动态代理来实现 Plugin(Interceptor)的嵌入。

2.2.2、通过getMapper()方法来获取Mapper实例。

同上。

2.3 执行对应查询代码

同上。

2.3.1 找到接口对应的sql

同上。

2.3.2 执行sql,封装结果

缺省情况下executor是SimpleExecutorSimpleExecutor.doQuery()方法内容如下:

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
  1. 执行sql,获得结果
  2. 把结果转换成接口中声明的类型然后返回

其中1.调用了configuration.newStatementHandler()来构造一个 statement,其内部实现如下:

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

我们看到了interceptorChain.pluginAll(statementHandler),这里的逻辑便和2.2.2一致了,用 Java 的动态代理,把Interceptor的代码嵌入到Handler实例中。

这样一来,如 SQLStatsInterceptor 的注解中声明的那样,在调用 StatementHandler.parameterize时,便会回调Interceptor的代码逻辑了。

Linear Regression with one variable

Notion

  • m = Number of training examples
  • x’s = “input” variable / features
  • y’s = “output” variable / “target” variable

Hypothesis

\[ h_{\theta}(x) = \theta_0 + \theta_1x \]

Cost function

\[
J(\theta)
= \frac{1}{2m} \sum_{i=1}^m (h_\theta(x^{(i)}) - y^{(i)})^2
= \frac{1}{2m} \sum_{i=1}^m (\theta_0+\theta_1x^{(i)} - y^{(i)})^2
\]

Gredient descent

\[
\theta_j = \theta_j - \alpha \frac{\partial}{\partial\theta_j} J(\Theta) \\
= \theta_j - \alpha \frac{\partial}{\partial\theta_j} (\frac{1}{2m} \sum_{i=1}^m (h_\theta(x^{(i)}) - y^{(i)})^2) \\
= \theta_j - \alpha \frac{1}{m} \sum_{i=1}^m (h_\theta(x^{(i)}) - y^{(i)})(x^{(i)}_j)
\]

  • j = 0

\[
\theta_0 = \theta_0 - \alpha \frac{1}{m} \sum_{i=1}^m(\theta_0+\theta_1x^{(i)}-y^{(i)})
\]

  • j = 1

\[
\theta_1 = \theta_1 - \alpha \frac{1}{m} \sum_{i=1}^m(\theta_0+\theta_1x^{(i)}-y^{(i)})x^{(i)}
\]

(simultaneously update \(\theta_j\) for all j)

Linear Regression with multiple variables

Notion

  • \(n\) = number of features
  • \(x^{(i)}\) = input of \(i^{th}\) training example
  • \(x^{(i)}_j\) = input of \(i^{th}\) training example 's \(j^{th}\) feature.
  • \(x^{(2)}_3\) = 2

Hypothesis derivation

  • Previousely: \( h_{\theta}(x) = \theta_0 + \theta_1x \)
  • New algorithm: \( h_{\theta}(x) = \theta_0 + \theta_1x_1 + \theta_2x_2 + \theta_3x_3 \)

\(
X =
\begin{bmatrix} x_0\\x_1\\x_2\\\vdots\\x_n \end{bmatrix}
\in R^{n+1}
\)

\(
\Theta =
\begin{bmatrix} \theta_0\\\theta_1\\\theta_2\\\vdots\\\theta_n \end{bmatrix}
\in R^{n+1}
\)

  • For convenience of notation, define \(x_0=1\)

\(
h_{\theta}(x)
= \theta_0x_0 + \theta_1x_1 + \theta_2x_2 + \theta_3x_3
= \Theta^TX
\)

Hypothesis

\[
\begin{align}
h_{\theta}(x)
&= \Theta^TX \\
&= \theta_0x_0 + \theta_1x_1 + \theta_2x_2+ \cdots + \theta_nx_n
\end{align}
\]

Cost function

\[
\begin{align}
J(\theta)
&= J(\theta_0, \theta_1, \cdots, \theta_n) \\
&= \frac{1}{2m} \sum_{i=1}^{m}(h_\theta(x^{(i)})-y^{(i)})^2
\end{align}
\]

Gradient descent

\[
\begin{align}
\theta_j &= \theta_j - \alpha \frac{\partial}{\partial\theta_j} J(\Theta) \\
&= \theta_j - \alpha \frac{\partial}{\partial\theta_j} (\frac{1}{2m} \sum_{i=1}^m (h_\theta(x^{(i)}) - y^{(i)})^2) \\
&= \theta_j - \alpha \frac{1}{m} \sum_{i=1}^m (h_\theta(x^{(i)}) - y^{(i)})(x^{(i)}_j)
\end{align}
\]

  • j = 0

\[
\theta_0 = \theta_0 - \alpha \frac{1}{m} \sum_{i=1}^m(\theta_0+\theta_1x^{(i)}-y^{(i)})(x^{(i)}_0)
\]

  • j = 1

\[
\theta_1 = \theta_1 - \alpha \frac{1}{m} \sum_{i=1}^m(\theta_0+\theta_1x^{(i)}-y^{(i)})x^{(i)}(x^{(i)}_1)
\]

……

Feature Scaling

  • Make sure features are on a similar scale.
  • Get every feature into approximately a \(-1 \leq x_i \leq 1\) range.

Mean normalization

Replace \(x_i\) with \(x_i-\mu_i\) to make features have approximately zero mean
(Do not apply to ).

E.g.
\[ x_1 = \frac{size-1000}{2000} \]
\[ x_2 = \frac{bedrooms-2}{5} \]

\[x_1 = \frac{x_1-\mu_1}{\sigma_1}\]
\[x_2 = \frac{x_2-\mu_2}{\sigma_2}\]

Learning rate

While doing gradient decent

\[
\theta_j = \theta_j - \alpha \frac{\partial}{\partial\theta_j} J(\Theta)
\]

“Debugging”: How to make sure gradient descent is working correctly.

How to choose learning rate

Summary:
* If \(\alpha\) too small: slow convergence
* If \(\alpha\) too large: \(J(\theta)\) may not decrease on every iteration; may not converge.

To choose \(\alpha\), try

\(\cdots, 0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 1, \cdots\)

Normal Equation

Normal equation: Method to solve for analy2cally.

\[ \theta = (X^TX)^{-1}X^Ty \]

Gradient Descent Normal Equation
1 Need choose \(\alpha\) No need to choose \(\alpha\)
2 Need many iterations Do not need to iterate
3 Works well even n is large Need to compute \((X^TX)^{-1}\), time complexity O(n3)
4 Slow if n is very large

Linear Algebra:

Matrices and vectors

  • Matrix:
  • Dimension of matrix:
  • Matrix Elements
  • Vector

Addition and scalar multiplication

Matrix Addition

Scalar Multiplication

Matrix-vector multiplication

Matrix-matrix multiplication

Matrix multiplication properties

Inverse and transpose

  • Inverse
  • Transponse

Octave Tutorial

Basic operations

Moving data around

Computing on data

Plotting data

Control statements: for, while, if statements

Vectorial implementation

\[
\begin{split}
&\theta_j = \theta_j - \alpha \frac{\partial}{\partial\theta_j} J(\Theta) \\
&= \theta_j - \alpha \frac{\partial}{\partial\theta_j} (\frac{1}{2m} \sum_{i=1}^m (h_\theta(x^{(i)} - y^{(i)}))^2) \\
&= \theta_j - \alpha \frac{\partial}{\partial\theta_j} (\frac{1}{2m} \sum_{i=1}^m (\theta_0+\theta_1x^{(i)} - y^{(i)})^2) \\
&= \theta_j - \alpha (\frac{1}{m} \sum_{i=1}^m (1+x^{(i)}))
\end{split}
\]

posted in JavaWeb 

1、问题:

不同层代码之间的异常情况,通过『返回码包装』还是『通过异常捕获』解决?

Java应用中,上层的代码会调用下层的代码:比如Controller层调用Service层,Service层调用Dao层等等。

上层代码调用下层代码会出错,比如UserServer.getUserById()这个方法,根据ID获取用户的详细信息。

2、解决方式:

错误的情况下,下层代码需要告诉上层代码错误发生了。
此时有两种通知上层调用方:
方法一:通过返回码的包装
方法二:通过抛出异常

两种详细说明如下:

2.1、 方法一:通过返回码的包装

UserServer.getUserById()返回一个Result<User>类

  • 函数定义如下:
Result<User> UserServer.getUserById(int id);
  • 其中Result定义如下:
public class Result<T> {
    private RetCodeEnum retCodeEnum;
    private T info;
}
  • 调用方调用姿势如下:
Result<User> userResult = userServer.getUserById(5);
if (RetCodeEnum.OK != userResult.getRetCodeEnum) {
    //给用户相应提示
}

//继续处理业务

2.2、方法二:通过抛出异常

UserServer.getUserById()抛出业务异常,

  • 函数定义如下:
User UserServer.getUserById(int id) throw BusinessException;
  • 其中BusinessException定义如下:
public class RetCodeEnumException extends Exception {
    private RetCodeEnum retCodeEnum;
}
  • 调用方调用姿势如下:
User user = userServer.getUserById(5);
//继续处理业务
  • 最后来一个全局的ExceptionHandler()来处理业务异常:
    protected BaseResp handleException(HttpServletRequest req, Exception e) {

        BaseResp baseResp;
        if (e instanceof BusinessException) {
            BusinessException businessException = (BusinessException) e;
            baseResp = new BaseResp(new Result(BusinessException));
        } else {
            baseResp = new BaseResp(new Result(RetCodeEnum.SYS_BUSY));
            LOGGER.error(e.getMessage(), e);
        }

        return baseResp;
    }

3、两种方式比较

第二种调用方式,把错误情况的处理统一到一个Handler处理,完爆第一种方式,理由如下:

3.1、大大减少了重复代码量。

比如你的Java应用有10层,最底层的一个函数有一种错误的情况,那上面9层调用这个方法的100个函数都要加上这么一句:

if (RetCodeEnum.OK != userResult.getRetCodeEnum) {
    //给用户相应提示
}

3.2、大大提升了安全性 && 健壮性

第一种方式,每次调用都要添加以上几行代码,而一旦忘记,就意味着 安全问题 or 莫名其妙的错误提示!

第二种方式,因为Exception的抛出是强制的,一旦错误情况发生,一定会中断正常的流程。

3.3、简化错误逻辑处理的代码

某天想在所有错误的情况下打印日志,

  • 第一种方式,需要在所有调用的地方打印日志,LOGGER.error("xxx")可能要写几千次;
  • 第二种方式,只需要在ExceptionHandler中添加一次LOGGER.error("xxx")即可。

3.4、错误信息更加 清晰结构化

如果
底层的一个方法a出错,导致方法 b、c、d 都产生问题
底层的一个方法m出错,导致方法 o、p、q 都产生问题

  • 如果用返回码的方式处理

那么日志里看起来是这样的:

a error
b error
c error
d error
m error
o error
p error
q error

很难区分到底出现了几次问题,每次出问题前因后果是什么

  • 如果用异常的方式处理

那么日志里看起来是这样的:

A Exception:
    Statck trace:
        d
        c
        b
        a
    Cause by:
        a
        
B Exception:
    Stack trace:
        q
        p
        o
        m
    Cause by:
        m

容易可以看出产生了几次问题,问题的前因后果是怎样的。

4、结论

鉴于以上种种好处,Java中错误情况的处理尽量通过 Exception 解决,而非通过返回码的包装解决。