一、 事务概述

●在JavaEE企业级开发的应用领域,为了保证数据的完整性一致性,必须引入数据库事务的概念,所以事务管理是企业级应用程序开发中必不可少的技术。

●事务就是一组由于逻辑上紧密关联而合并成一个整体(工作单元)的多个数据库操作,这些操作要么都执行要么都不执行

●事务的四个关键属性(ACID)

原子性(atomicity):“原子”的本意是“不可再分”,事务的原子性表现为一个事务中涉及到的多个操作在逻辑上缺一不可。事务的原子性要求事务中的所有操作要么都执行,要么都不执行。

一致性(consistency):“一致”指的是数据的一致,具体是指:所有数据都处于满足业务规则的一致性状态。一致性原则要求:一个事务中不管涉及到多少个操作,都必须保证事务执行之前数据是正确的,事务执行之后数据仍然是正确的。如果一个事务在执行的过程中,其中某一个或某几个操作失败了,则必须将其他所有操作撤销,将数据恢复到事务执行之前的状态,这就是回滚

隔离性(isolation):在应用程序实际运行过程中,事务往往是并发执行的,所以很有可能有许多事务同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。隔离性原则要求多个事务在并发执行过程中不会互相干扰

持久性(durability):持久性原则要求事务执行完成后,对数据的修改永久的保存下来,不会因各种系统错误或其他意外情况而受到影响。通常情况下,事务对数据的修改应该被写入到持久化存储器中。

二、 Spring事务管理

2.1 编程式事务管理

2.1.1使用原生的JDBC API进行事务管理

[1]获取数据库连接Connection对象

[2]取消事务的自动提交

[3]执行操作

[4]正常完成操作时手动提交事务

[5]执行失败时回滚事务

[6]关闭相关资源

例:

//1.获取Connection对象

Connection conn = JDBCUtils.getConnection();

try {

    //2.开启事务:取消自动提交

    conn.setAutoCommit(false);

    //3.执行操作

    //4.正常完成操作,手动提交事务

    conn.commit();

}catch(Exception e) {

    //5.回滚事务

    conn.rollBack();

}finally{

    //6.释放资源

}

2.1.2评价

使用原生的JDBC API实现事务管理是所有事务管理方式的基石,同时也是最典型的编程式事务管理。编程式事务管理需要将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在使用编程的方式管理事务时,必须在每个事务操作中包含额外的事务管理代码。相对于核心业务而言,事务管理的代码显然属于非核心业务,如果多个模块都使用同样模式的代码进行事务管理,显然会造成较大程度的代码冗余

2.2 声明式事务管理

大多数情况下声明式事务比编程式事务管理更好:它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。

事务管理代码的固定模式作为一种横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理

Spring在不同的事务管理API之上定义了一个抽象层,通过配置的方式使其生效,从而让应用程序开发人员不必了解事务管理API的底层实现细节,就可以使用Spring的事务管理机制。

Spring既支持编程式事务管理,也支持声明式的事务管理。

2.3 Spring提供的事务管理器

Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。

Spring的核心事务管理抽象是PlatformTransactionManager。它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。

事务管理器可以以普通的bean的形式声明在Spring IOC容器中。

2.4 事务管理器的主要实现

①DataSourceTransactionManager:在应用程序中只需要处理一个数据源,而且通过JDBC存取。

②JtaTransactionManager:在JavaEE应用服务器上用JTA(Java Transaction API)进行事务管理

③HibernateTransactionManager:用Hibernate框架存取数据库

3 测试数据准备

3.1 需求

3.2 数据库表

CREATE TABLE book (

isbn VARCHAR (50) PRIMARY KEY,

book_name VARCHAR (100),

price INT

) ;

CREATE TABLE book_stock (

isbn VARCHAR (50) PRIMARY KEY,

stock INT,

CHECK (stock > 0)

) ;

CREATE TABLE account (

username VARCHAR (50) PRIMARY KEY,

balance INT,

CHECK (balance > 0)

) ;

INSERT INTO account (`username`,`balance`) VALUES ('Tom',100000);

INSERT INTO account (`username`,`balance`) VALUES ('Jerry',150000);

INSERT INTO book (`isbn`,`book_name`,`price`) VALUES ('ISBN-001','book01',100);

INSERT INTO book (`isbn`,`book_name`,`price`) VALUES ('ISBN-002','book02',200);

INSERT INTO book (`isbn`,`book_name`,`price`) VALUES ('ISBN-003','book03',300);

INSERT INTO book (`isbn`,`book_name`,`price`) VALUES ('ISBN-004','book04',400);

INSERT INTO book (`isbn`,`book_name`,`price`) VALUES ('ISBN-005','book05',500);

INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-001',1000);

INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-002',2000);

INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-003',3000);

INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-004',4000);

INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-005',5000);

4 初步实现

4.1 配置文件

<!-- 配置事务管理器 -->

<bean id="transactionManager"

class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

<property name="dataSource" ref="dataSource"/>

</bean>

<!-- 启用事务注解 -->

<tx:annotation-driven transaction-manager="transactionManager"/>

4.2 在需要进行事务控制的方法上加注解

@Transactional

5 事务的传播行为

5.1 简介

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为。系统默认属性为:REQUIRED。

事务传播属性可以在@Transactional注解的propagation属性中定义。

5.2 测试

5.3 说明

①REQUIRED传播行为

当bookService的purchase()方法被另一个事务方法checkout()调用时,它默认会在现有的事务内运行。这个默认的传播行为就是REQUIRED。因此在checkout()方法的开始和终止边界内只有一个事务。这个事务只在checkout()方法结束的时候被提交,结果用户一本书都买不了。

②REQUIRES_NEW传播行为

表示该方法必须启动一个新事务,并在自己的事务内运行。如果有事务在运行,就应该先挂起它。

5.4 补充

在Spring 2.x事务通知中,可以像下面这样在<tx:method>元素中设定传播事务属性。

6 事务的隔离级别

6.1 数据库事务并发问题

假设现在有两个事务:Transaction01和Transaction02并发执行。

脏读

[1]Transaction01将某条记录的AGE值从20修改为30。

[2]Transaction02读取了Transaction01更新后的值:30。

[3]Transaction01回滚,AGE值恢复到了20。

[4]Transaction02读取到的30就是一个无效的值。

不可重复读

[1]Transaction01读取了AGE值为20。

[2]Transaction02将AGE值修改为30。

[3]Transaction01再次读取AGE值为30,和第一次读取不一致。

幻读

[1]Transaction01读取了STUDENT表中的一部分数据。

[2]Transaction02向STUDENT表中插入了新的行。

[3]Transaction01读取了STUDENT表时,多出了一些行。

6.2 隔离级别

数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。

读未提交:READ UNCOMMITTED

允许Transaction01读取Transaction02未提交的修改。

读已提交:READ COMMITTED

要求Transaction01只能读取Transaction02已提交的修改。

可重复读:REPEATABLE READ

确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。

串行化:SERIALIZABLE

确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

⑤各个隔离级别解决并发问题的能力见下表

脏读

不可重复读

幻读

READ UNCOMMITTED

READ COMMITTED

REPEATABLE READ

SERIALIZABLE

⑥各种数据库产品对事务隔离级别的支持程度

Oracle

MySQL

READ UNCOMMITTED

×

READ COMMITTED

REPEATABLE READ

×

√(默认)

SERIALIZABLE

6.3 在Spring中指定事务隔离级别

①注解

用@Transactional注解声明式地管理事务时可以在@Transactional的isolation属性中设置隔离级别

②XML

在Spring 2.x事务通知中,可以在<tx:method>元素中指定隔离级别

7 触发事务回滚的异常

7.1 默认情况

捕获到RuntimeException或Error时回滚,而捕获到编译时异常不回滚。

7.2 设置途经

①注解

@Transactional 注解

[1]rollbackFor属性:指定遇到时必须进行回滚的异常类型,可以为多个

[2]noRollbackFor属性:指定遇到时不回滚的异常类型,可以为多个

②XML

在Spring 2.x事务通知中,可以在<tx:method>元素中指定回滚规则。如果有不止一种异常则用逗号分隔。

8 事务的超时和只读属性

8.1 简介

由于事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响。

如果一个事物只读取数据但不做修改,数据库引擎可以对这个事务进行优化。

超时事务属性:事务在强制回滚之前可以保持多久。这样可以防止长期运行的事务占用资源。

只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。

8.2 设置

①注解

@Transaction注解

②XML

在Spring 2.x事务通知中,超时和只读属性可以在<tx:method>元素中进行指定

9 基于XML文档的声明式事务配置

<!-- 配置事务切面 -->

<aop:config>

<aop:pointcut

expression="execution(* com.atguigu.tx.component.service.BookShopServiceImpl.purchase(..))"

id="txPointCut"/>

<!-- 将切入点表达式和事务属性配置关联到一起 -->

<aop:advisor advice-ref="myTx" pointcut-ref="txPointCut"/>

</aop:config>

<!-- 配置基于XML的声明式事务  -->

<tx:advice id="myTx" transaction-manager="transactionManager">

<tx:attributes>

<!-- 设置具体方法的事务属性 -->

<tx:method name="find*" read-only="true"/>

<tx:method name="get*" read-only="true"/>

<tx:method name="purchase"

isolation="READ_COMMITTED"

No-rollback-for="java.lang.ArithmeticException,java.lang.NullPointerException"

propagation="REQUIRES_NEW"

read-only="false"

timeout="10"/>

</tx:attributes>

</tx:advice>

10.声明式事务例:

①导入SQL文件:declaration_transaction.sql

该文件内容如下,可直接复制到记事本,文件名后缀为.sql,通过数据库(如:Navicat Premium等)运行该sql文件就好。

/*
SQLyog Ultimate v9.20
MySQL - 5.1.37-community : Database - tx
*********************************************************************
*/ /*!40101 SET NAMES utf8 */; /*!40101 SET SQL_MODE=''*/; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`tx` /*!40100 DEFAULT CHARACTER SET gb2312 */; USE `tx`; /*Table structure for table `account` */ DROP TABLE IF EXISTS `account`; CREATE TABLE `account` (
`username` varchar(50) NOT NULL,
`balance` int(11) DEFAULT NULL,
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=gb2312; /*Data for the table `account` */ insert into `account`(`username`,`balance`) values ('Jerry',800),('Tom',100000); /*Table structure for table `book` */ DROP TABLE IF EXISTS `book`; CREATE TABLE `book` (
`isbn` varchar(50) NOT NULL,
`book_name` varchar(100) DEFAULT NULL,
`price` int(11) DEFAULT NULL,
PRIMARY KEY (`isbn`)
) ENGINE=InnoDB DEFAULT CHARSET=gb2312; /*Data for the table `book` */ insert into `book`(`isbn`,`book_name`,`price`) values ('ISBN-001','book01',100),('ISBN-002','book02',200),('ISBN-003','book03',300),('ISBN-004','book04',400),('ISBN-005','book05',500); /*Table structure for table `book_stock` */ DROP TABLE IF EXISTS `book_stock`; CREATE TABLE `book_stock` (
`isbn` varchar(50) NOT NULL,
`stock` int(11) DEFAULT NULL,
PRIMARY KEY (`isbn`)
) ENGINE=InnoDB DEFAULT CHARSET=gb2312; /*Data for the table `book_stock` */ insert into `book_stock`(`isbn`,`stock`) values ('ISBN-001',1000),('ISBN-002',2000),('ISBN-003',3000),('ISBN-004',4000),('ISBN-005',5000); /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

declaration_transaction.sql

②创建一个动态web工程

1.加入jar包

com.springsource.net.sf.cglib-2.2.0.jar

com.springsource.org.aopalliance-1.0.0.jar

com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar

commons-logging-1.1.3.jar

spring-aop-4.0.0.RELEASE.jar

spring-aspects-4.0.0.RELEASE.jar

spring-beans-4.0.0.RELEASE.jar

spring-context-4.0.0.RELEASE.jar

spring-core-4.0.0.RELEASE.jar

spring-expression-4.0.0.RELEASE.jar

spring-jdbc-4.0.0.RELEASE.jar

spring-orm-4.0.0.RELEASE.jar

spring-tx-4.0.0.RELEASE.jar

然后是mysql驱动包即C3P0的jar包:

c3p0-0.9.1.2.jar

mysql-connector-java-5.1.37-bin.jar

2.创建一份jdbc.properties文件

jdbc.user=root

jdbc.passowrd=123456

jdbc.url=jdbc:mysql://localhost:3306/tx

jdbc.driver=com.mysql.jdbc.Driver

3.在spring配置文件中配置数据源

<!-- 引入外部属性文件 -->

<context:property-placeholder location="classpath:jdbc.properties"/>

<!-- 配置数据源 -->

<bean id="comboPooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">

<property name="user" value="${jdbc.user}"></property>

<property name="password" value="${jdbc.passowrd}"></property>

<property name="jdbcUrl" value="${jdbc.url}"></property>

<property name="driverClass" value="${jdbc.driver}"></property>

</bean>

4.测试数据源:

public class TestDataSource {

  private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");

  @Test

  public void test() throws SQLException {
    //获取数据源类
    DataSource bean = ioc.getBean(DataSource.class);
    //检验是否能够连接
    System.out.println(bean.getConnection());   }
}

  

5.配置jdbcTemplate:

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
  <property name="dataSource" ref="comboPooledDataSource"></property>
</bean>

6.创建Dao类

<!-- 在xml文件中设置扫描的包 -->

<context:component-scan base-package="com.neuedu"></context:component-scan>

  

@Repository
public class BookDao { @Autowired
private JdbcTemplate jdbcTemplate; //[1]根据isbn的值查询书的价格
public Integer findPriceByIsbn(String isbn){
  String sql = "SELECT price FROM book WHERE isbn = ?";
  Integer price = jdbcTemplate.queryForObject(sql, Integer.class, isbn);
  return price;
} //[2]根据isbn的值减少书的库存,假设每次都只买1本书
public void updateStockByIsbn(String isbn){
  String sql = "UPDATE book_stock SET stock=stock-1 WHERE isbn=? ";
  jdbcTemplate.update(sql, isbn);
} //[3]根据用户名减少用户账户中的余额,减少的额度就是书的价格
public void updateBalance(String username,int price){
  String sql = "UPDATE account SET balance=balance-? WHERE username=? ";
  jdbcTemplate.update(sql,price, username);
} public void updatePriceByIsbn(String isbn,int price){
  String sql="UPDATE book SET price=? WHERE isbn=? ";
  jdbcTemplate.update(sql, price,isbn);
} public int findPriceByIsbn(String isbn){
  String sql = "SELECT price FROM book WHERE isbn = ?";
  Integer price = jdbcTemplate.queryForObject(sql, Integer.class, isbn);
  return price;
}
}

  

测试上面的该方法:

 public class TestDataSource {

  private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
  private BookDao bean = ioc.getBean(BookDao.class);
  @Test
  public void test01() throws SQLException {
    int findPriceByIsbn = bean.findPriceByIsbn("ISBN-005");
    System.out.println(findPriceByIsbn);
  }
}

然后在dao类中继续编写如下方法:

//[2]根据isbn的值减少书的库存,假设每次都只买1本书
public void updateStockByIsbn(String isbn){
  String sql = "UPDATE book_stock SET stock = stock -1 WHERE isbn = ?";
  jdbcTemplate.update(sql, isbn);
}

  

继续测试:

 public class TestDataSource {

private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
private BookDao bean = ioc.getBean(BookDao.class); @Test
public void test02() throws SQLException {
bean.updateStockByIsbn("ISBN-004");
}
}

  

继续编写Dao类中的方法:

//[3]根据用户名减少用户账户中的余额,减少的额度就是书的价格
public void updateBalance(String userName,int price){
  String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
  jdbcTemplate.update(sql, price,userName);
}

  

继续测试:

@Test
public void test03() throws SQLException {
  bean.updateBalance("Tom", 1000);
}

  

7.创建Service层:

@Service
public class BookService {
  @Autowired
  private BookDao bookDao;   public void doCash(String isbn,String username){
    int price = bookDao.findPriceByIsbn(isbn);
    bookDao.updateStockByIsbn(isbn);
    bookDao.updateBalance(username, price);
  }
}

  

8.注意哦:上面doCash方法中调用的三个方法应该在同一个事务中,要么同时成功,要么同时失败!

先统一设置一下:

UPDATE account SET balance = 10000;
UPDATE book_stock SET stock = 1000;

然后先正常测试一下service中的doCash方法:

public class TestDataSource {
  private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
  private BookDao bean = ioc.getBean(BookDao.class);
  private BookService bookService = ioc.getBean(BookService.class);
  
  @Test
  public void test04() throws SQLException {
    bookService.doCash("ISBN-001","Tom");
  }
}

  

由于此时该方法没在事务中,如果doCash方法调用dao层的方法的时候,在中间位置出现了错误,此时就会造成一部分数据改掉了,而

另一部分数据没有改掉,这就麻烦了,所以此时应该加入事务机制!

9.如果想开启事务,就需要在spring的配置文件中配置事务管理器

 <!-- 配置事务管理器,并为事务管理器配置数据源!-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="comboPooledDataSource"></property>
</bean>
<!-- 开启基于注解的声明式事务功能,需要设置transaction-manager属性-->
<!-- 如果 事务管理器的id正好是transaction-manager的默认值transactionManager,则可以省略-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>

然后在service层的doCash方法上加上:@Transactional注解这样就开启了事务!需要注意的是事务一般是加在service层的!

③数据库操作

[1]根据isbn的值查询书的价格

[2]根据isbn的值减少书的库存,假设每次都只买1本书

[3]根据用户名减少用户账户中的余额,减少的额度就是书的价格

Spring框架——事务处理(编程式和声明式)的更多相关文章

  1. spring事务的开启方式(编程式和声明式)

    1.编程式事务:编码方式实现事务管理(代码演示为JDBC事务管理) Spring实现编程式事务,依赖于2大类,分别是上篇文章提到的PlatformTransactionManager,与模版类Tran ...

  2. Spring编程式和声明式事务实例讲解

    Java面试通关手册(Java学习指南):https://github.com/Snailclimb/Java_Guide 历史回顾: 可能是最漂亮的Spring事务管理详解 Spring事务管理 S ...

  3. 二十 Spring的事务管理及其API&事务的传播行为,编程式&声明式(xml式&注解式,底层AOP),转账案例

    Spring提供两种事务方式:编程式和声明式(重点) 前者需要手写代码,后者通过配置实现. 事务的回顾: 事务:逻辑上的一组操作,组成这组事务的各个单元,要么全部成功,要么全部失败 事务的特性:ACI ...

  4. spring事务管理——编程式事务、声明式事务

    本教程将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务.通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之. 先决条件 本教程假定您已经掌握了 ...

  5. spring入门3-jdbcTemplate简单使用和声明式事务

    1.JdbcTemplate简单使用 1.1.引入相关依赖包 <dependency> <groupId>mysql</groupId> <artifactI ...

  6. 事务之三:编程式事务、声明式事务(XML配置事务、注解实现事务)

    Spring2.0框架的事务处理有两大类: JdbcTemplate操作采用的是JDBC默认的AutoCommit模式,也就是说我们还无法保证数据操作的原子性(要么全部生效,要么全部无效),如: Jd ...

  7. Spring框架之事务源码完全解析

    Spring框架之事务源码完全解析   事务的定义及特性: 事务是并发控制的单元,是用户定义的一个操作序列.这些操作要么都做,要么都不做,是一个不可分割的工作单位.通过事务将逻辑相关的一组操作绑定在一 ...

  8. spring笔记--事务管理之声明式事务

    事务简介: 事务管理是企业级应用开发中必不可少的技术,主要用来确保数据的完整性和一致性, 事务:就是一系列动作,它们被当作一个独立的工作单元,这些动作要么全部完成,要么全部不起作用. Spring中使 ...

  9. Spring框架的重要问题

    这篇文章总结了一些关于Spring框架的重要问题,这些问题都是你在面试或笔试过程中可能会被问到的. 目录 Spring概述 依赖注入 Spring Beans Spring注解 Spring的对象访问 ...

随机推荐

  1. 由于用mpu6050模块,所以要用上i2c通信原理。

    i2c通信原理 i2c总线只有两根双向信号线,一根是数据线SDA,一根是时钟线SCL. 每个接到i2c总线上的器件都有唯一的地址,主机与其他器件之间的数据传送可以是由主机发送给其他器件.主机为发送器, ...

  2. 初学 Python(十四)——生成器

    初学 Python(十四)--生成器 初学 Python,主要整理一些学习到的知识点,这次是生成器. # -*- coding:utf-8 -*- ''''' 生成式的作用: 减少内存占有,不用一次性 ...

  3. vim 配置插件

    vim插件可以用bundle管理,我这里面用的是一个开源的Vundle工具,git上操作步骤说的很清楚https://github.com/VundleVim/Vundle.vim 之前PluginI ...

  4. java中权限修饰符protected的使用注意事项

    java中四种权限修饰符, 平时编码中最常用的其实public和private, 虽然对protected的概念一直都知道, 但真正使用时才发现有些偏差. protected表示被其修饰的成员可以被本 ...

  5. Redux源码分析之compose

    Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...

  6. 16. leetcode 404. Sum of Left Leaves

    Find the sum of all left leaves in a given binary tree. Example:     3    / \   9  20     /  \    15 ...

  7. ASP.NET Core 快速入门【第二弹-实战篇】

    上篇讲了asp.net core在linux上的环境部署.今天我们将做几个小玩意实战一下.用到的技术和工具有mysql.websocket.AngleSharp(爬虫html解析).nginx多站点部 ...

  8. Java中的char究竟能存中文吗?

    今天面试被问到"Java中的char能存中文吗?",我回答有的字能有的字不能,结果被嘲笑了,不过我也忘了字符编码的相关知识所以也没能解释.晚上查了下资料,记录一下. 网上搜索这个问 ...

  9. 【机器学习PAI实践二】人口普查统计

    一.背景 感谢大家关注玩转数据系列文章,我们希望通过在阿里云机器学习平台上提供demo数据并搭建相关的实验流程的方式来帮助大家学习如何通过算法来挖掘数据中的价值.本系列文章包含详细的实验流程以及相关的 ...

  10. python学习===实现定时发送,方法一

    #比如每3秒打印一次helloworld:from threading import Timer def printHello(): print "Hello World" t = ...