`
jinnianshilongnian
  • 浏览: 21434321 次
  • 性别: Icon_minigender_1
博客专栏
5c8dac6a-21dc-3466-8abb-057664ab39c7
跟我学spring3
浏览量:2405044
D659df3e-4ad7-3b12-8b9a-1e94abd75ac3
Spring杂谈
浏览量:2997694
43989fe4-8b6b-3109-aaec-379d27dd4090
跟开涛学SpringMVC...
浏览量:5631469
1df97887-a9e1-3328-b6da-091f51f886a1
Servlet3.1规范翻...
浏览量:257571
4f347843-a078-36c1-977f-797c7fc123fc
springmvc杂谈
浏览量:1593160
22722232-95c1-34f2-b8e1-d059493d3d98
hibernate杂谈
浏览量:248971
45b32b6f-7468-3077-be40-00a5853c9a48
跟我学Shiro
浏览量:5847542
Group-logo
跟我学Nginx+Lua开...
浏览量:698161
5041f67a-12b2-30ba-814d-b55f466529d5
亿级流量网站架构核心技术
浏览量:780453
社区版块
存档分类
最新评论

在应用层通过spring特性解决数据库读写分离

 
阅读更多

 

如何配置mysql数据库的主从?

单机配置mysql主从:http://my.oschina.net/god/blog/496

 

常见的解决数据库读写分离有两种方案

1、应用层

http://neoremind.net/2011/06/spring实现数据库读写分离

目前的一些解决方案需要在程序中手动指定数据源,比较麻烦,后边我会通过AOP思想来解决这个问题。

 

2、中间件

mysql-proxyhttp://hi.baidu.com/geshuai2008/item/0ded5389c685645f850fab07

Amoeba for MySQLhttp://www.iteye.com/topic/188598http://www.iteye.com/topic/1113437

 

此处我们介绍一种在应用层的解决方案,通过spring动态数据源和AOP来解决数据库的读写分离。

 

该方案目前已经在一个互联网项目中使用了,而且可以很好的工作。

 

该方案目前支持

一读多写;当写时默认读操作到写库、当写时强制读操作到读库。

 

考虑未来支持

读库负载均衡、读库故障转移等。

 

使用场景

不想引入中间件,想在应用层解决读写分离,可以考虑这个方案;

建议数据访问层使用jdbcibatis,不建议hibernate。

 

优势

应用层解决,不引入额外中间件;

在应用层支持『当写时默认读操作到写库』,这样如果我们采用这种方案,在写操作后读数据直接从写库拿,不会产生数据复制的延迟问题;

应用层解决读写分离,理论支持任意数据库。

 

 

缺点

1、不支持@Transactional注解事务,此方案要求所有读方法必须是read-only=true,因此如果是@Transactional,这样就要求在每一个读方法头上加@Transactional 且readOnly属性=true,相当麻烦。 :oops: 

2、必须按照配置约定进行配置,不够灵活。

 

两种方案



方案1:当只有读操作的时候,直接操作读库(从库);

        当在写事务(即写主库)中读时,也是读主库(即参与到主库操作),这样的优势是可以防止写完后可能读不到刚才写的数据;

 

此方案其实是使用事务传播行为为:SUPPORTS解决的。

 


方案2:当只有读操作的时候,直接操作读库(从库);

        当在写事务(即写主库)中读时,强制走从库,即先暂停写事务,开启读(读从库),然后恢复写事务。

此方案其实是使用事务传播行为为:NOT_SUPPORTS解决的。

 

核心组件

cn.javass.common.datasource.ReadWriteDataSource:读写分离的动态数据源,类似于AbstractRoutingDataSource,具体参考javadoc

cn.javass.common.datasource.ReadWriteDataSourceDecision:读写库选择的决策者,具体参考javadoc

cn.javass.common.datasource.ReadWriteDataSourceProcessor:此类实现了两个职责(为了减少类的数量将两个功能合并到一起了):读/写动态数据库选择处理器、通过AOP切面实现读/写选择,具体参考javadoc

 

具体配置

1、数据源配置

1.1、写库配置

  	<bean id="writeDataSource" class="org.logicalcobwebs.proxool.ProxoolDataSource">
		<property name="alias" value="writeDataSource"/>
		<property name="driver" value="${write.connection.driver_class}" />
		<property name="driverUrl" value="${write.connection.url}" />
		<property name="user" value="${write.connection.username}" />
		<property name="password" value="${write.connection.password}" />
		<property name="maximumConnectionCount" value="${write.proxool.maximum.connection.count}"/>
		<property name="minimumConnectionCount" value="${write.proxool.minimum.connection.count}" />
		<property name="statistics" value="${write.proxool.statistics}" />
		<property name="simultaneousBuildThrottle" value="${write.proxool.simultaneous.build.throttle}"/>
	</bean>

 

1.2、读库配置

    <bean id="readDataSource1" class="org.logicalcobwebs.proxool.ProxoolDataSource">
        <property name="alias" value="readDataSource"/>
        <property name="driver" value="${read.connection.driver_class}" />
        <property name="driverUrl" value="${read.connection.url}" />
        <property name="user" value="${read.connection.username}" />
        <property name="password" value="${read.connection.password}" />
        <property name="maximumConnectionCount" value="${read.proxool.maximum.connection.count}"/>
        <property name="minimumConnectionCount" value="${read.proxool.minimum.connection.count}" />
        <property name="statistics" value="${read.proxool.statistics}" />
        <property name="simultaneousBuildThrottle" value="${read.proxool.simultaneous.build.throttle}"/>
    </bean> 

1.3、读写动态库配置   

通过writeDataSource指定写库,通过readDataSourceMap指定从库列表,从库列表默认通过顺序轮询来使用读库,具体参考javadoc

    <bean id="readWriteDataSource" class="cn.javass.common.datasource.ReadWriteDataSource">
        <property name="writeDataSource" ref="writeDataSource"/>
        <property name="readDataSourceMap">
           <map>
              <entry key="readDataSource1" value-ref="readDataSource1"/>
              <entry key="readDataSource2" value-ref="readDataSource1"/>
              <entry key="readDataSource3" value-ref="readDataSource1"/>
              <entry key="readDataSource4" value-ref="readDataSource1"/>
           </map>
        </property>
    </bean> 

 

2XML事务属性配置

所以读方法必须是read-only(必须,以此来判断是否是读方法)。

    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="save*" propagation="REQUIRED" />
            <tx:method name="add*" propagation="REQUIRED" />
            <tx:method name="create*" propagation="REQUIRED" />
            <tx:method name="insert*" propagation="REQUIRED" />
            <tx:method name="update*" propagation="REQUIRED" />
            <tx:method name="merge*" propagation="REQUIRED" />
            <tx:method name="del*" propagation="REQUIRED" />
            <tx:method name="remove*" propagation="REQUIRED" />
            
            <tx:method name="put*" read-only="true"/>
            <tx:method name="query*" read-only="true"/>
            <tx:method name="use*" read-only="true"/>
            <tx:method name="get*" read-only="true" />
            <tx:method name="count*" read-only="true" />
            <tx:method name="find*" read-only="true" />
            <tx:method name="list*" read-only="true" />
            
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice> 

 

3、事务管理器

事务管理器管理的是readWriteDataSource

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="readWriteDataSource"/>
    </bean> 

 

4、读/写动态数据库选择处理器

根据之前的txAdvice配置的事务属性决定是读/写,具体参考javadoc

forceChoiceReadWhenWrite:用于确定在如果目前是写(即开启了事务),下一步如果是读,是直接参与到写库进行读,还是强制从读库读,具体参考javadoc

    <bean id="readWriteDataSourceTransactionProcessor" class="cn.javass.common.datasource.ReadWriteDataSourceProcessor">
       <property name="forceChoiceReadWhenWrite" value="false"/>
    </bean> 

 

5、事务切面和读/写库选择切面

    <aop:config expose-proxy="true">
        <!-- 只对业务逻辑层实施事务 -->
        <aop:pointcut id="txPointcut" expression="execution(* cn.javass..service..*.*(..))" />
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
        
        <!-- 通过AOP切面实现读/写库选择 -->
        <aop:aspect order="-2147483648" ref="readWriteDataSourceTransactionProcessor">
           <aop:around pointcut-ref="txPointcut" method="determineReadOrWriteDB"/>
        </aop:aspect>
    </aop:config> 

1、事务切面一般横切业务逻辑层;

2、此处我们使用readWriteDataSourceTransactionProcessor的通过AOP切面实现读/写库选择功能,order=Integer.MIN_VALUE(即最高的优先级),从而保证在操作事务之前已经决定了使用读/写库。

 

6、测试用例

只要配置好事务属性(通过read-only=true指定读方法)即可,其他选择读/写库的操作都交给readWriteDataSourceTransactionProcessor完成。

 

可以参考附件的:

cn.javass.readwrite.ReadWriteDBTestWithForceChoiceReadOnWriteFalse

cn.javass.readwrite.ReadWriteDBTestWithNoForceChoiceReadOnWriteTrue

 

 

可以下载附件的代码进行测试,具体选择主/从可以参考日志输出。

 

暂不想支持@Transactional注解式事务。

 

PS:欢迎拍砖指正。   

 

 

 

  • 大小: 19.7 KB
  • 大小: 15.3 KB
20
3
分享到:
评论
30 楼 shui_jiangnan 2017-01-05  
下载的例子好像并不能切换数据源
29 楼 风中追风_ly 2016-12-07  
风中追风_ly 写道
Cool3Rocks 写道
应该是 shiro 加载的优先级太高了,引用的 bean 还没有创建完,头疼

  同样这个问题,LZ 是怎么解决的?

解决了 刚才看了下  是因为ShiroFilterFactoryBean也实现了BeanPostProcessor接口  我们只需要自定义数据源的processer再实现Ordered接口,同时指定此接口加载顺序优先shiro就可以了
@Override
public int getOrder() {
return -1;
}
28 楼 风中追风_ly 2016-12-07  
Cool3Rocks 写道
应该是 shiro 加载的优先级太高了,引用的 bean 还没有创建完,头疼

  同样这个问题,LZ 是怎么解决的?
27 楼 sljiang 2016-08-06  
楼主写的很好,学习了
请教一个问题,以下场景如何处理?
新增一笔订单(写事务,访问主库),然后页面跳转到订单列表(只读事务,访问从库),如果从库有延迟,就不一定能够显示刚增加的新订单。
26 楼 kimimo 2016-03-17  
8还好还好哈
25 楼 北域游龙 2015-12-21  
feirou520 写道
我在使用BeanPostProcessor拦截NameMatchTransactionAttributeSource的时候,怎么也拦截不到这个类,我想知道有什么可能会造成这样的情况

我的也是,永远都不会匹配NameMatchTransactionAttributeSource类型,,然后导致读map永远是{}
24 楼 feirou520 2015-08-25  
我在使用BeanPostProcessor拦截NameMatchTransactionAttributeSource的时候,怎么也拦截不到这个类,我想知道有什么可能会造成这样的情况
23 楼 linjin19880218 2015-06-03  
问下开涛,你这个用读写分离用hibernate,既然事务拦截不到了。知道为什么么?
22 楼 210006373 2015-06-02  
  :    
21 楼 rainbow.cai158 2015-01-09  
修正。。主写从读- -
20 楼 rainbow.cai158 2015-01-09  
现在有更简单的实现方法了,使用mysql replication,只要设置jdbc connection readOnly为true ,即可实现主读从写
19 楼 hjjwind 2014-12-15  
taok88 写道
楼主的文章写的很好,值得学习。

关于sprng配置动态数据源,我这边遇到一个问题.
我没有用aop来配置哪些方法连写的库,哪些方法连读的库。
我直接是在指定的查询方法里调用ReadWriteDataSourceDecision.markRead()方法,方法执行完之后再调用reset方法。

但是这样会有一个问题,用户并发量大时候,就会有数据源错乱,在写数据库操作的方法里会引用到只读的数据库。
楼主问题下这是什么情况啊?



——估计是WEB服务器都是使用的线程池,有线程变量没清空。
18 楼 taok88 2014-11-20  
楼主的文章写的很好,值得学习。

关于sprng配置动态数据源,我这边遇到一个问题.
我没有用aop来配置哪些方法连写的库,哪些方法连读的库。
我直接是在指定的查询方法里调用ReadWriteDataSourceDecision.markRead()方法,方法执行完之后再调用reset方法。

但是这样会有一个问题,用户并发量大时候,就会有数据源错乱,在写数据库操作的方法里会引用到只读的数据库。
楼主问题下这是什么情况啊?
17 楼 1215811695 2014-11-05  
楼主给力,spring博客总的好多,有时间好好看看。
我现在有个问题,就是楼主说不支持@Transactional。我想问一下,假如在方法上面使用了@Transactional并且没有加readonly属性,会有什么情况?是在事务的传播时会有问题吗?

我看楼主通过aop切面来定义事务管理的,假如一个service即满足了aop又满足了@transaction.是不是会开两次事务(spring不太熟,望见凉)
16 楼 Cool3Rocks 2014-05-22  
应该是 shiro 加载的优先级太高了,引用的 bean 还没有创建完,头疼
15 楼 Cool3Rocks 2014-05-22  
Cool3Rocks 写道
开涛你好,我在使用BeanPostProcessor拦截NameMatchTransactionAttributeSource的时候, 报了如下异常,导致拦截失败。请问有解决办法么

19:46:49.865 [localhost-startStop-1] INFO  org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean '(inner bean)#470' of type [class org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)


目前确定是与shiro的配置造成的,

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <!-- override these for application-specific URLs if you like:-->
        <property name="loginUrl" value="${shiro.login.url}"/>
        <property name="unauthorizedUrl" value="${shiro.unauthorizedUrl}"/>
        <!-- The 'filters' property is not necessary since any declared javax.servlet.Filter bean  -->
        <!-- defined will be automatically acquired and available via its beanName in chain        -->
        <!-- definitions, but you can perform instance overrides or name aliases here if you like: -->
        <property name="filters">
            <util:map>
                <entry key="authc" value-ref="formAuthenticationFilter"/>
                <entry key="logout" value-ref="logoutFilter"/>
                <entry key="sysUser" value-ref="sysUserFilter"/>
                <entry key="onlineSession" value-ref="onlineSessionFilter"/>
                <entry key="syncOnlineSession" value-ref="syncOnlineSessionFilter"/>
                <entry key="jCaptchaValidate" value-ref="jCaptchaValidateFilter"/>
                <entry key="develop" value-ref="developFilter"/>
            </util:map>
        </property>
        <property name="filterChainDefinitions">
            <value>
                /static/** = anon
                /anon/** = anon
                /login.json = anon
                /jcaptcha* = anon
                /logout = logout
                /login = jCaptchaValidate,authc
                /** = develop,sysUser,onlineSession,user,syncOnlineSession,perms,roles
            </value>
        </property>
    </bean>

去掉才可以
14 楼 Cool3Rocks 2014-05-21  
开涛你好,我在使用BeanPostProcessor拦截NameMatchTransactionAttributeSource的时候, 报了如下异常,导致拦截失败。请问有解决办法么

19:46:49.865 [localhost-startStop-1] INFO  org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean '(inner bean)#470' of type [class org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
13 楼 jinnianshilongnian 2012-11-24  
icewind4096 写道
可以讲讲请问如果有如下的使用情况,该如何解决,
管理数据库 manager.db 里面记录每个分公司账册数据库名称
Jiangsu.db
Hunan.db
Zhejiang.db
manager.db中的分账册数据库名是动态的,并不是一个固定记录,我只定义了指向manager.db的
datasource,用于登录是获得各自的数据库名
主要是如何动态的产生到各自数据库的数据源,
我考虑了两个方法
1.通过过滤器+session来实现
2.使用aop,在servise的before中+session来实现,
这些数据库结构一样 系统登录到管理数据库 根据客户端提交的标识 决定客户端登录的默认数据库是那个 但是我不知道这个时候数据源的数据库该如何切换 数据源不是都被在配置文件中定义死了吗。我用spring框架该如何实现。或者有什么变通的办法 后端数据库是sqlserver. 难道只有在操作时都带上库名.
例如
select * from [jiangsu].stock. 查询江苏库存
select * from [zhejiang].stock. 查询浙江库存么
如果可能可以给我个demo,刚刚上手spring, 谢谢

过滤器+ThreadLocal+DynamicDataSource完成:
1、通过过滤器根据用户登录信息选择库 存到ThreadLocal
2、DynamicDataSource通过ThreadLocal选择库
3、退出过滤器时删除ThreadLocal

DynamicDataSource可以搜索下,很多例子
12 楼 icewind4096 2012-11-24  
可以讲讲请问如果有如下的使用情况,该如何解决,
管理数据库 manager.db 里面记录每个分公司账册数据库名称
Jiangsu.db
Hunan.db
Zhejiang.db
manager.db中的分账册数据库名是动态的,并不是一个固定记录,我只定义了指向manager.db的
datasource,用于登录是获得各自的数据库名
主要是如何动态的产生到各自数据库的数据源,
我考虑了两个方法
1.通过过滤器+session来实现
2.使用aop,在servise的before中+session来实现,
这些数据库结构一样 系统登录到管理数据库 根据客户端提交的标识 决定客户端登录的默认数据库是那个 但是我不知道这个时候数据源的数据库该如何切换 数据源不是都被在配置文件中定义死了吗。我用spring框架该如何实现。或者有什么变通的办法 后端数据库是sqlserver. 难道只有在操作时都带上库名.
例如
select * from [jiangsu].stock. 查询江苏库存
select * from [zhejiang].stock. 查询浙江库存么
如果可能可以给我个demo,刚刚上手spring, 谢谢
11 楼 jinnianshilongnian 2012-11-09  
kimmking 写道
自定义一个annotation @ReadOnly,把读方法或写方法上标记上@ReadOnly
然后再AOP处理时换成某个读DS,可以避免方法命名约束。。。


根本之道还是根据sql解析得到是什么操作。例如淘宝的tddl






嗯,这个也想到了,但是我觉得如果读方法太多很多的方法头上都有@ReadOnly,不爽

引用
根本之道还是根据sql解析得到是什么操作。例如淘宝的tddl
这个同意,不过如果简单的读写分离用此方案也挺好 ,百合现在用呢。

相关推荐

    在应用层通过spring特性解决数据库读写分离代码

    在应用层通过spring特性解决数据库读写分离代码

    在应用层透过spring解决数据库读写分离

    技术分享:在应用层透过spring解决数据库读写分离

    spring杂谈 作者zhang KaiTao

    1.25 在应用层通过spring特性解决数据库读写分离 1.26 context:component-scan扫描使用上的容易忽略的use-default-filters 1.27 idea内嵌jetty运行springmvc项目报ConversionFailedException 1.28 springmvc 3.2 @...

    ShardingSphere:SpringBoot2+MybatisPlus读写分离+分表

    他们均提供标准化的数据分片、读写分离、柔性事务和数据治理功能,可适用于如Java同构、异构语言、容器、云原生等各种多样化的应用场景。Sharding-JDBC可以通过Java,YAML,Spring命名空间和Spring Boot S

    tianti-tool:本Project持续提供常见应用的包装类,目前包括:redis、kafka、mysql数据库读写分离操作、swagger的demo使用。后续会持续更多的应用操作

    tianti-tool本Project持续提供常见应用的包装类,目前包括:redis、kafka、mysql数据库读写分离操作、swagger的demo使用等等。该Project目前都是基于Spring Boot来构建,后续会持续更多的应用操作。1、tianti-kafka...

    ShardingSphere:SpringBoot2+MybatisPlus+读写分离+分库分表

    ShardingSphere:SpringBoot2+MybatisPlus+读写分离+分库分表 ...他们均提供标准化的数据分片、读写分离、柔性事务和数据治理功能,可适用于如Java同构、异构语言、容器、云原生等各种多样化的应用场景。S

    动态数据源演示:基于事务的读写分离

    应用层读写分离的改进背景数据库读写分离是先进的Web架构不可替代的一环,其主要提升在于:主从职能单一,主写从读,可以极大程度地减轻X锁和S锁的竞争,并且可以进行针对性调优请求分流,减少主库压力当读成为DB...

    ShardingSphere:SpringBoot2+MybatisPlus+Swagger读写分离

    他们均提供标准化的数据分片、读写分离、柔性事务和数据治理功能,可适用于如Java同构、异构语言、容器、云原生等各种多样化的应用场景。Sharding-JDBC可以通过Java,YAML,Spring命名空间和Spring Boot Starter四种...

    支持多数据库的ORM框架ef-orm.zip

    大部分OLTP应用系统到最后都不免要使用SQL/JPQL,然而没有一个很好的方法解决SQL在多种数据库下兼容性的问题。 EF-ORM中采用了独特的SQL解析和改写技术,能够主动检查并确保SQL语句或者SQL片段在各个数据库上的兼容...

    javaweb图书馆管理系统项目源码

    该系统主要用于图书馆的图书管理、读者管理、借阅管理等功能,分为前台和后台两部分。...后台提供图书分类管理、图书管理、读者管理、借阅管理等功能。 主要功能模块: ...8. 数据库读写分离、连接池等技术应用 该

    【spring-boot-seckill分布式秒杀系统 v1.0】从0到1构建的java秒杀系统源码+安装说明

    4、后端秒杀业务逻辑,基于Redis 或者 Zookeeper 分布式锁,Kafka 或者 Redis 做消息队列,DRDS数据库中间件实现数据的读写分离。 优化思路 1、分流、分流、分流,重要的事情说三遍,再牛逼的机器也抵挡不住高级别的...

    Java思维导图xmind文件+导出图片

    基于Mycat实习MySQL数据库读写分离 基于Mycat实战之数据库切分策略剖析 Mycat全局表、Er表、分片预警分析 Nginx 基于OpenResty部署应用层Nginx以及Nginx+lua实战 Nginx反向代理服务器及负载均衡服务器配置实战...

    Apache ShardingSphere分布式数据库中间层生态圈-其他

    使用Database Mesh,访问数据库的应用和数据库终将形成一个巨大的网格体系,应用和数据库只需在网格体系中对号入座即可,它们都是被啮合层所治理的对象。 混合架构 ShardingSphere-JDBC采用无中心化架构,适用于Java...

    毕业设计-分布式 RPC公司销售管理系统(源代码+说明)

    拆分多个数据库实现读写分离 使用Redis实现分布式锁技术 系统在生活中的应用十分广泛,无论是个人还是企业,在日常生活中都需要系统,不仅可以提高工作效率和质量,也可以提高数据准确性,以下是系统的好处: 1. ...

    compass:Compass是一个轻量级的嵌入式分布式数据库访问层框架

    Compass是搜狗商业平台研发部开发的一套轻量级的分布式数据库访问框架,支持单库、主从库读写分离、分库、分库之后再分表、从库负载均衡和HA等使用场景,并且在框架层面提供了主从反延迟策略。Compass采用Spring配置...

    低清版 大型门户网站是这样炼成的.pdf

    10.4.4 spring 2接管业务逻辑层 675 10.5 小结 677 第11章 温故知新—打造购物车与订单管理系统 679 11.1 购物车与订单管理系统需求分析 679 11.2 购物车及订单管理系统功能设计 680 11.3 购物车自助管理 682 ...

    【白雪红叶】JAVA学习技术栈梳理思维导图.xmind

    读写分离 性能优化架构能力 代码级别 关联代码优化 cache对其 分支预测 copy on write 内联优化 系统优化 cache 延迟计算 数据预读 异步 轮询与通知 内存池 模块化 工程架构能力 开发语言 运维与...

    iBATIS实战

    书中既详实地介绍了iBATIS的设计理念和基础知识,也讨论了动态SQL、高速缓存、DAD框架等高级主题,还讲解了iBATIS在实际开发中的应用。书的最后给出了一个设计优雅、层次清晰的示例程序JGameStore,该示例涵盖全书的...

    asp.net知识库

    在ASP.NET中使用WINDOWS验证方式连接SQL SERVER数据库 改进ADO.Net数据库访问方式 ASP.NET 2.0 绑定高级技巧 简单实用的DataSet更新数据库的类+总结 [ADO.NET]由数据库触发器引发的问题 为ASP.NET封装的SQL数据库...

Global site tag (gtag.js) - Google Analytics