从源码分析 Spring 基于注解的事务
在spring引入基于注解的事务(@Transactional)之前,我们一般都是如下这样进行拦截事务的配置:
<!-- 拦截器方式配置事务 -->
<tx:advice id="transactionAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="append*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="modify*" propagation="REQUIRED" />
<tx:method name="edit*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="remove*" propagation="REQUIRED" />
<tx:method name="repair" propagation="REQUIRED" />
<tx:method name="delAndRepair" propagation="REQUIRED" /> <tx:method name="get*" propagation="SUPPORTS" />
<tx:method name="find*" propagation="SUPPORTS" />
<tx:method name="load*" propagation="SUPPORTS" />
<tx:method name="search*" propagation="SUPPORTS" />
<tx:method name="datagrid*" propagation="SUPPORTS" /> <tx:method name="*" propagation="SUPPORTS" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="transactionPointcut" expression="execution(* net.aazj.service..*Impl.*(..))" />
<aop:advisor pointcut-ref="transactionPointcut" advice-ref="transactionAdvice" />
</aop:config>
这种方式明显的缺点是,不太容易理解,并且限定了service层的方法名称的前缀,没有模板的话写起来也很难,很容易写错。
因此在spring中引入了基于注解的事务配置方法之后,我们应该抛弃这种事务配置方法了。基于注解 @Transactional 的事务配置具有简单,灵活的优点。下面看一个例子:
@Service("userService")
@Transactional
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
@Transactional (propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=true)
public User getUser(int userId) {
return userMapper.getUser(userId);
}
@Transactional
public void addUser(String username){
userMapper.addUser(username);
// int i = 1/0; // 测试事务的回滚
}
@Transactional (rollbackFor = BaseBusinessException.class)
public void addAndDeleteUser(String username, int id) throws BaseBusinessException{
userMapper.addUser(username);
this.m1();
userMapper.deleteUserById(id);
}
private void m1() throws BaseBusinessException {
throw new BaseBusinessException("xxx");
}
}
首先在service类上声明了@Transactional,表明类中的所有方法都需要运行在事务中,然后在方法中可以指定具体的事务特性,方法中的@Transactional会覆盖类上的@Transactional。
下面我们从源码的角度(从源码的学习可以给我们实打实的比较深入理解,而且不会出错,二手资料总是会有时延的)来探究一下它们:
public @interface Transactional {
/**
* A qualifier value for the specified transaction.
* <p>May be used to determine the target transaction manager,
* matching the qualifier value (or the bean name) of a specific
* {@link org.springframework.transaction.PlatformTransactionManager}
* bean definition.
*/
String value() default "";
/**
* The transaction propagation type.
* Defaults to {@link Propagation#REQUIRED}.
* @see org.springframework.transaction.interceptor.TransactionAttribute#getPropagationBehavior()
*/
Propagation propagation() default Propagation.REQUIRED;
/**
* The transaction isolation level.
* Defaults to {@link Isolation#DEFAULT}.
* @see org.springframework.transaction.interceptor.TransactionAttribute#getIsolationLevel()
*/
Isolation isolation() default Isolation.DEFAULT;
/**
* The timeout for this transaction.
* Defaults to the default timeout of the underlying transaction system.
* @see org.springframework.transaction.interceptor.TransactionAttribute#getTimeout()
*/
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
/**
* {@code true} if the transaction is read-only.
* Defaults to {@code false}.
* <p>This just serves as a hint for the actual transaction subsystem;
* it will <i>not necessarily</i> cause failure of write access attempts.
* A transaction manager which cannot interpret the read-only hint will
* <i>not</i> throw an exception when asked for a read-only transaction.
* @see org.springframework.transaction.interceptor.TransactionAttribute#isReadOnly()
*/
boolean readOnly() default false;
/**
* Defines zero (0) or more exception {@link Class classes}, which must be a
* subclass of {@link Throwable}, indicating which exception types must cause
* a transaction rollback.
* <p>This is the preferred way to construct a rollback rule, matching the
* exception class and subclasses.
* <p>Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class clazz)}
*/
Class<? extends Throwable>[] rollbackFor() default {};
/**
* Defines zero (0) or more exception names (for exceptions which must be a
* subclass of {@link Throwable}), indicating which exception types must cause
* a transaction rollback.
* <p>This can be a substring, with no wildcard support at present.
* A value of "ServletException" would match
* {@link javax.servlet.ServletException} and subclasses, for example.
* <p><b>NB: </b>Consider carefully how specific the pattern is, and whether
* to include package information (which isn't mandatory). For example,
* "Exception" will match nearly anything, and will probably hide other rules.
* "java.lang.Exception" would be correct if "Exception" was meant to define
* a rule for all checked exceptions. With more unusual {@link Exception}
* names such as "BaseBusinessException" there is no need to use a FQN.
* <p>Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(String exceptionName)}
*/
String[] rollbackForClassName() default {};
/**
* Defines zero (0) or more exception {@link Class Classes}, which must be a
* subclass of {@link Throwable}, indicating which exception types must <b>not</b>
* cause a transaction rollback.
* <p>This is the preferred way to construct a rollback rule, matching the
* exception class and subclasses.
* <p>Similar to {@link org.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(Class clazz)}
*/
Class<? extends Throwable>[] noRollbackFor() default {};
/**
* Defines zero (0) or more exception names (for exceptions which must be a
* subclass of {@link Throwable}) indicating which exception types must <b>not</b>
* cause a transaction rollback.
* <p>See the description of {@link #rollbackForClassName()} for more info on how
* the specified names are treated.
* <p>Similar to {@link org.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(String exceptionName)}
*/
String[] noRollbackForClassName() default {};
}
public @interface Transactional
注解@Transactional的属性有:propagation, isolation, timeout, readOnly, rollbackFor, rollbackForClassName, noRollbackFor, noRollbackForClassName
propagation, isolation, timeout, readOnly都有默认值,而rollbackFor, rollbackForClassName, noRollbackFor, noRollbackForClassName默认值都是空的。
我们具体看下我们可能会用到的属性:propagation, isolation, readOnly, rollbackFor
1)propagation指定事务的传播属性:
public enum Propagation {
/**
* Support a current transaction, create a new one if none exists.
* Analogous to EJB transaction attribute of the same name.
* <p>This is the default setting of a transaction annotation.
*/
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
/**
* Support a current transaction, execute non-transactionally if none exists.
* Analogous to EJB transaction attribute of the same name.
* <p>Note: For transaction managers with transaction synchronization,
* PROPAGATION_SUPPORTS is slightly different from no transaction at all,
* as it defines a transaction scope that synchronization will apply for.
* As a consequence, the same resources (JDBC Connection, Hibernate Session, etc)
* will be shared for the entire specified scope. Note that this depends on
* the actual synchronization configuration of the transaction manager.
* @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization
*/
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
/**
* Support a current transaction, throw an exception if none exists.
* Analogous to EJB transaction attribute of the same name.
*/
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
/**
* Create a new transaction, and suspend the current transaction if one exists.
* Analogous to the EJB transaction attribute of the same name.
* <p>Note: Actual transaction suspension will not work out-of-the-box on
* all transaction managers. This in particular applies to JtaTransactionManager,
* which requires the {@code javax.transaction.TransactionManager} to be
* made available it to it (which is server-specific in standard J2EE).
* @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
*/
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
/**
* Execute non-transactionally, suspend the current transaction if one exists.
* Analogous to EJB transaction attribute of the same name.
* <p>Note: Actual transaction suspension will not work on out-of-the-box
* on all transaction managers. This in particular applies to JtaTransactionManager,
* which requires the {@code javax.transaction.TransactionManager} to be
* made available it to it (which is server-specific in standard J2EE).
* @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
*/
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
/**
* Execute non-transactionally, throw an exception if a transaction exists.
* Analogous to EJB transaction attribute of the same name.
*/
NEVER(TransactionDefinition.PROPAGATION_NEVER),
/**
* Execute within a nested transaction if a current transaction exists,
* behave like PROPAGATION_REQUIRED else. There is no analogous feature in EJB.
* <p>Note: Actual creation of a nested transaction will only work on specific
* transaction managers. Out of the box, this only applies to the JDBC
* DataSourceTransactionManager when working on a JDBC 3.0 driver.
* Some JTA providers might support nested transactions as well.
* @see org.springframework.jdbc.datasource.DataSourceTransactionManager
*/
NESTED(TransactionDefinition.PROPAGATION_NESTED);
private final int value;
Propagation(int value) { this.value = value; }
public int value() { return this.value; }
}
public enum Propagation
propagation可以取如下的值:REQUIRED, SUPPORTS, MANDATORY, REQUIRES_NEW, NOT_SUPPORTED, NEVER, NESTED
我们一般会只用:REQUIRED(默认值), SUPPORTS,其它的取值基本上不使用(具体可以参考上面源码中的注释,已经很详细了)。
propagation=Propagation.REQUIRED:表示该方法或类必须要事务的支持,如果已经是在一个事务中被调用,那么就使用该事务,如果没有在一个事务中,那么就新建一个事务。
propagation=Propagation.SUPPORTS:表示该方法或类支持事务,如果已经是在一个事务中被调用,那么就使用该事务,如果没有在一个事务中,也可以。
2)isolation指定事务的隔离级别:
public enum Isolation {
/**
* Use the default isolation level of the underlying datastore.
* All other levels correspond to the JDBC isolation levels.
* @see java.sql.Connection
*/
DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),
/**
* A constant indicating that dirty reads, non-repeatable reads and phantom reads
* can occur. This level allows a row changed by one transaction to be read by
* another transaction before any changes in that row have been committed
* (a "dirty read"). If any of the changes are rolled back, the second
* transaction will have retrieved an invalid row.
* @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED
*/
READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),
/**
* A constant indicating that dirty reads are prevented; non-repeatable reads
* and phantom reads can occur. This level only prohibits a transaction
* from reading a row with uncommitted changes in it.
* @see java.sql.Connection#TRANSACTION_READ_COMMITTED
*/
READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),
/**
* A constant indicating that dirty reads and non-repeatable reads are
* prevented; phantom reads can occur. This level prohibits a transaction
* from reading a row with uncommitted changes in it, and it also prohibits
* the situation where one transaction reads a row, a second transaction
* alters the row, and the first transaction rereads the row, getting
* different values the second time (a "non-repeatable read").
* @see java.sql.Connection#TRANSACTION_REPEATABLE_READ
*/
REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),
/**
* A constant indicating that dirty reads, non-repeatable reads and phantom
* reads are prevented. This level includes the prohibitions in
* {@code ISOLATION_REPEATABLE_READ} and further prohibits the situation
* where one transaction reads all rows that satisfy a {@code WHERE}
* condition, a second transaction inserts a row that satisfies that
* {@code WHERE} condition, and the first transaction rereads for the
* same condition, retrieving the additional "phantom" row in the second read.
* @see java.sql.Connection#TRANSACTION_SERIALIZABLE
*/
SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);
private final int value;
Isolation(int value) { this.value = value; }
public int value() { return this.value; }
}
public enum Isolation
isolation可以取如下的值:DEFAULT, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
我们一般只使用:DEFAULT(默认值),因为我们一般是直接在数据库的层面上来设置事务的隔离级别,很少会在应用层来设置隔离基本。
isolation=Isolation.DEFAULT:表示使用下层数据库指定的隔离级别
3)readOnly为只读,利于数据库优化器进行优化。默认值为 false。所以对于只对数据库进行读取的方法,我们可以如下指定:
@Transactional (propagation=Propagation.SUPPORTS)
public User getUser(int userId) {
return userMapper.getUser(userId);
}
表示:有事务则使用当前事务,没有事务则不使用事务。最大限度的利于数据库的优化器进行优化。
如果一定要使用事务的话,也可以这样使用readOnly=true来优化:
@Transactional (propagation=Propagation.REQUIRED,readOnly=true)
public User getUser(int userId) {
return userMapper.getUser(userId);
}
这就是 readOnly 的作用。表示有事务则使用当前事务,如果没有事务,则新建一个只读事务。其实 上面的 propagation=Propagation.REQUIRED也是可以去掉的,因为他是默认值!
@Transactional (readOnly=true)
public User getUser(int userId) {
return userMapper.getUser(userId);
}
表示强制事务,并且是 只读事务。readOnly 要注意有点的是,readOnly只有在 有事务时,才会生效,如果没有事务,那么 readOnly 是会被忽略的。比如:
@Transactional(propagation=Propagation.SUPPORTS, readonly=true)
表示的就是 没有事务 也是可以的,有事务的话,就一定是 只读事务。
4)rollbackFor,其实该属性也很少使用,而且经常被误用。表示抛出什么异常时,会回滚事务。异常分为受检异常(必须进行处理或者重新抛出)和非受检异常(可以不进行处理)。在遇到非受检异常时,事务是一定会进行回滚的。rollbackFor用于指定对于何种受检异常发生时,进行回滚。因为受检异常,我们必须进行处理或者重新抛出,所以只有一种情况下我们要使用rollbackFor来指定,就是我们不处理异常,直接抛出受检异常,并且我们需要方法在抛出该异常时,进行回滚,如上面的例子:
@Transactional (rollbackFor = BaseBusinessException.class)
public void addAndDeleteUser(String username, int id) throws BaseBusinessException{
userMapper.addUser(username);
this.m1();
userMapper.deleteUserById(id);
}
private void m1() throws BaseBusinessException {
throw new BaseBusinessException("xxx");
}
因为 m1 方法抛出受检异常,我们在 addAndDeleteUser 方法中不对该异常进行处理,而是直接抛出,如果我们希望 userMapper.addUser(username) 和 userMapper.deleteUserById(id) 要么都成功,要么都失败,此时我们则应该指定:rollbackFor = BaseBusinessException.class ,进行回滚。
所以只有我们的方法声明要抛出一个受检异常时,我们才应该使用 rollbackFor 属性来进行处理。如果我们在 addAndDeleteUser 方法中对 m1 方法的受检异常进行了处理,那么就没有必要使用 rollbackFor 了:
public void addAndDeleteUser(String username, int id){
userMapper.addUser(username);
try{
this.m1();
}catch(BaseBusinessException e){
// 处理异常,比如记录进日志文件等
}
userMapper.deleteUserById(id);
}
因为我们处理了 m1 方法的异常,那么就不会有受检异常导致 userMapper.addUser(username) 和 userMapper.deleteUserById(id) 这两个方法一个执行成功,一个没有执行。而非受检异常默认就会回滚。
受检异常是必须进行处理或者重新声明抛出的。只有声明重新抛出受检异常时,才会需要使用 rollbackFor 属性。所以下面的方式就属于滥用 rollbackFor 了:
@Transactional (rollbackFor=Exception.class)
public void addAndDeleteUser2(String username, int id){
userMapper.addUser(username);
userMapper.deleteUserById(id);
}
因为:受检异常是必须进行处理或者重新声明抛出,而我们既没有进行处理,也没有重新抛出,就说明他绝对不可能会抛出受检异常了。而只会抛出未受检异常,而未受检异常,默认就会回滚,所以上面的 @Transactional (rollbackFor=Exception.class) 完全是多余的。
总结:
1)@Transactional 的默认值为:@Transactional (propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=false,
timeout=TransactionDefinition.TIMEOUT_DEFAULT),默认值已经适合绝大多数情况,所以我们一般使用 @Transactional 进行注解就够了。
2)只有当默认值不符合我们的需要时才给@Transactional的属性指定值,一般也就指定:propagation=Propagation.SUPPORTS 和 readOnly=true,其它的属性和值一般很少使用非默认值。所以我们前面的UserServiceImpl类可以重构如下:
@Service("userService")
@Transactional
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
@Transactional (readOnly=true)
public User getUser(int userId) {
return userMapper.getUser(userId);
}
public void addUser(String username){
userMapper.addUser(username);
int i = 1/0; // 测试事务的回滚
}
public void deleteUser(int id){
userMapper.deleteUserById(id);
// int i = 1/0; // 测试事务的回滚
}
@Transactional (rollbackFor = BaseBusinessException.class)
public void addAndDeleteUser(String username, int id) throws BaseBusinessException{
userMapper.addUser(username);
this.m1();
userMapper.deleteUserById(id);
}
private void m1() throws BaseBusinessException {
throw new BaseBusinessException("xxx");
}
}
addUser 和 deleteUser 因为会继承上的 @Transactional ,所以无需另外指定了,只有当类上指定的 @Transactional 不适合时,才需要另外在方法上进行指定。
3)所以我们只实际情况中,我们只需要使用下面三者来进行注解事务的配置:
@Transactional,
@Transactional (readOnly=true),
@Transactional (propagation=Propagation.SUPPORTS, readOnly=true),
@Transactional (rollbackFor = xxException.class),其它都可以保持默认值,其它的非默认值极少使用。
另外:
要使用@Transactional来进行注解事务配置,必须要在spring的配置文件中加入下面的配置说明,启用基于注解的配置:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 使用annotation定义事务 -->
<tx:annotation-driven transaction-manager="transactionManager" />
补充说明:
也许你会觉得奇怪,userMapper.getUser(userId); 这些涉及到数据库访问的方法,为什么不会抛出 SQLException 受检异常呢?如果手动注册驱动,然后获取链接,进行数据库操作时,是会有许多的受检异常要处理的:
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
// ...
}
String url="jdbc:mysql://localhost:3306/databasename";
try {
Connection conn = (Connection) DriverManager.getConnection(url,"username","password");
} catch (SQLException e) {
// ...
}
哪为什么在这里userMapper.getUser(userId); 就不需要处理受检异常呢?其实这是spring的功能,spring为了将我们从那些十分麻烦的受检异常比如SQLException中解救处理,将所有的数据库访问层的受检异常转嫁到 Spring 的 非受检异常RuntimeException 体系中来——DataAccessException。
从源码分析 Spring 基于注解的事务的更多相关文章
- Spring Ioc源码分析系列--@Autowired注解的实现原理
Spring Ioc源码分析系列--@Autowired注解的实现原理 前言 前面系列文章分析了一把Spring Ioc的源码,是不是云里雾里,感觉并没有跟实际开发搭上半毛钱关系?看了一遍下来,对我的 ...
- 精尽Spring Boot源码分析 - 剖析 @SpringBootApplication 注解
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- Dubbo SPI 机制源码分析(基于2.7.7)
Dubbo SPI 机制涉及到 @SPI.@Adaptive.@Activate 三个注解,ExtensionLoader 作为 Dubbo SPI 机制的核心负责加载和管理扩展点及其实现.本文以 E ...
- 源码分析系列1:HashMap源码分析(基于JDK1.8)
1.HashMap的底层实现图示 如上图所示: HashMap底层是由 数组+(链表)+(红黑树) 组成,每个存储在HashMap中的键值对都存放在一个Node节点之中,其中包含了Key-Value ...
- PHP扩展编写、PHP扩展调试、VLD源码分析、基于嵌入式Embed SAPI实现opcode查看
catalogue . 编译PHP源码 . 扩展结构.优缺点 . 使用PHP原生扩展框架wizard ext_skel编写扩展 . 编译安装VLD . Debug调试VLD . VLD源码分析 . 嵌 ...
- java基础系列之ConcurrentHashMap源码分析(基于jdk1.8)
1.前提 在阅读这篇博客之前,希望你对HashMap已经是有所理解的,否则可以参考这篇博客: jdk1.8源码分析-hashMap:另外你对java的cas操作也是有一定了解的,因为在这个类中大量使用 ...
- 【Spring Boot源码分析】@EnableAutoConfiguration注解(一)@AutoConfigurationImportSelector注解的处理
Java及Spring Boot新手,首次尝试源码分析,欢迎指正! 一.概述 @EnableAutoConfiguration注解是Spring Boot中配置自动装载的总开关.本文将从@Enable ...
- spring源码分析之cache注解
Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如EHCache 或者 OSCache),而是一个对缓存使用的抽象 ...
- Spring源码分析-从@ComponentScan注解配置包扫描路径到IoC容器中的BeanDefinition,经历了什么(一)?
阅前提醒 全文较长,建议沉下心来慢慢阅读,最好是打开Idea,点开Spring源码,跟着下文一步一步阅读,更加便于理解.由于笔者水平优先,编写时间仓促,文中难免会出现一些错误或者不准确的地方,恳请各位 ...
随机推荐
- UWP开发入门(十七)——判断设备类型及响应VirtualKey
蜀黍我做的工作跟IM软件有关,UWP同时会跑在电脑和手机上.电脑和手机的使用习惯不尽一致,通常我倾向于根据窗口尺寸来进行布局的变化,但是特定的操作习惯是依赖于设备类型,而不是屏幕尺寸的,比如聊天窗口的 ...
- Windows及Linux平台下的计时函数总结
本文对Windows及Linux平台下常用的计时函数进行总结,包括精度为秒.毫秒.微秒三种精度的各种函数.比如Window平台下特有的Windows API函数GetTickCount().timeG ...
- 在Linux上安装Oracle RAC 12 c(12.1) 虚拟机,一步一步向导
Oracle RAC 12 c(12.1)在Linux上安装虚拟机,一步一步向导 今天我们将看到如何安装 12 c版本1 RAC(真正的应用程序集群)数据库2 Linux 64位的虚拟机 使用VMWa ...
- linq之orderby子句
在Linq查询中,orderby 子句可以对查询结果集进行排序,可以升序也可以降序,排序关键字可以是多个.默认排序方式为升序. 下面的实例代码OrderQuery()中演示了orderby子句对查询的 ...
- NPOI根据模板生成chart图表导出Excel
导入NPOI的全部dll. 因为NPOI的API里面还没有对于Chart图表方面的操作,所以只能根据提示做好的图表作为模板,修改数据源的方法来改变图表. 注意:NPOI要用2003版以下的excel才 ...
- 微信公众平台入门开发教程.Net(C#)框架
一.序言 一直在想第一次写博客,应该写点什么好?正好最近在研究微信公众平台开发,索性就记录下,分享下自己的心得,也分享下本人简单模仿asp.net运行机制所写的通用的微信公众平台开发.Net(c#)框 ...
- js不间断滚动
CSS ul, li { margin: 0; padding: 0; } #scrollDiv { width: 300px; height: 25px; line-height: 25px; bo ...
- csharp:Nhibernate Procedure with CreateSQLQuery and GetNamedQuery
<?xml version="1.0" encoding="utf-8"?> <hibernate-mapping assembly=&quo ...
- [小北De编程手记] : Lesson 08 - Selenium For C# 之 PageFactory & 团队构建
本文想跟大家分享的是Selenium对PageObject模式的支持和自动化测试团队的构建.<Selenium For C#>系列的文章写到这里已经接近尾声了,如果之前的文章你是一篇篇的读 ...
- 发布ASP.NET Core程序到Linux生产环境
原文翻译:Publish to a Linux Production Environment 作者:Sourabh Shirhatti 在这篇文章里我们将介绍如何在 Ubuntu 14.04 Serv ...