一篇入门分布式事务框架Seata【AT模式】

摘要:
​Seata提供了AT、TCC、SAGA和XA事务模式,目录写在前面AT模式前提三大模块整体机制SeataServer(TC)安装TM/RM整合Seata总结AT模式前提基于支持本地ACID事务的关系型数据库。驱动全局事务提交或回滚。​TM(TransactionManager)-事务管理器​定义全局事务的范围:并驱动分⽀事务提交或回滚。TM会向TC注册全局事务记录;
写在前面

​ 本地事务很好保证要么所有操作都成功要么都失败,但是随着业务越来越复杂,单机版已经满足不了我们的需求,就需要项目从单体应用演变成分布式应用,然而随之也带来了一个问题,那就是如何保证多个微服务对DB的操作要么一起成功要么一起失败的问题,也就是分布式事务的问题。

​ 网上有一大堆分布式事务的解决方案的理论,转化落地的有Seata这么的框架。

官网首页对它的一句话描述是,Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

​ Seata提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。其中,AT 模式是一种无侵入的分布式事务解决方案,比较好上手,实现起来简单,应用的比较广泛,本文将介绍AT模式的实现。

目录

AT模式前提

  1. 基于支持本地 ACID 事务的关系型数据库。
  2. Java 应用,通过 JDBC 访问数据库。

三大模块

​ Seata 中有三⼤模块,分别是 TM、RM 和 TC。其中 TM 和 RM 是作为 Seata 的客户端与业务系统集成在⼀起,TC 作为 Seata 的服务端独⽴部署。

TC (Transaction Coordinator) - 事务协调者
​ 维护全局和分⽀事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器
​ 定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器
​ 管理分⽀事务处理的资源,与TC交谈以注册分⽀事务和报告分⽀事务的状态,并驱动分⽀事务提交或回滚。

一篇入门分布式事务框架Seata【AT模式】第1张

​ 在 Seata 中,分布式事务的执⾏流程:

  • TM 开启分布式事务,TM会 向 TC 注册全局事务记录;
  • 操作具体业务模块的数据库操作之前,RM 会向 TC 注册分⽀事务;
  • 当业务操作完事后,TM会通知 TC 提交/回滚分布式事务;
  • TC 汇总事务信息,决定分布式事务是提交还是回滚;
  • TC 通知所有 RM 提交/回滚 资源,事务⼆阶段结束。

整体机制

​ 两阶段提交协议的演变:

  • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
  • 二阶段:
    • 提交异步化,非常快速地完成。
    • 回滚通过一阶段的回滚日志进行反向补偿。

Seata Server(TC)安装

​ 本文使用的是v1.4.0

一篇入门分布式事务框架Seata【AT模式】第2张

  1. 配置registry.conf

    Seata Server要向注册中⼼进⾏注册,这样,其他服务就可以通过注册中⼼去发现 Seata Server,与Seata Server进⾏通信。Seata ⽀持多款注册中⼼服务:nacos 、eureka、redis、zk、consul、etcd3、sofa。我们项⽬中要使⽤ nacos注册中⼼,nacos服务的连接地址、注册的服务名,这需要在D:seataconf egistry.conf⽂件中进⾏配置:

    registry {
      # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
      type = "nacos"
      loadBalance = "RandomLoadBalance"
      loadBalanceVirtualNodes = 10
    
      nacos {
        application = "seata-server"
        serverAddr = "127.0.0.1:8848"
        group = "SEATA_GROUP"
        namespace = "b8107264-b340-4939-8d60-df77d18057dc"
        cluster = "default"
        username = "nacos"
        password = "nacos"
      }
    }
    
    config {
      # file、nacos 、apollo、zk、consul、etcd3
      type = "nacos"
    
      nacos {
        serverAddr = "127.0.0.1:8848"
        namespace = "b8107264-b340-4939-8d60-df77d18057dc"
        group = "SEATA_GROUP"
        username = "nacos"
        password = "nacos"
      }
    }
    
  2. 向nacos中添加配置信息

    • 下载配置config.txt

      一篇入门分布式事务框架Seata【AT模式】第3张

      Seata参数配置中针对每一个配置项有介绍。

    • 将config.txt⽂件放⼊seata⽬录下⾯

    • 修改config.txt信息

      Server端存储的模式(store.mode)现有file,db,redis三种。主要存储全局事务会话信息,分⽀事务信息, 锁记录表信息,Seata server默认是file模式。file只能⽀持单机模式, 如果想要⾼可⽤模式的话可以切换db或者redis,本文采⽤db数据库模式。

      注意这里我改了事务分组,默认是my_test_tx_group,我改成了my_tx_group。

      service.vgroupMapping.my_tx_group=default
      
      store.mode=db
      store.db.datasource=druid
      store.db.dbType=mysql
      store.db.driverClassName=com.mysql.jdbc.Driver
      store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true
      store.db.user=root
      store.db.password=123456
      store.db.minConn=5
      store.db.maxConn=30
      store.db.globalTable=global_table
      store.db.branchTable=branch_table
      store.db.queryLimit=100
      store.db.lockTable=lock_table
      store.db.maxWait=5000
      
    • 新建seata数据库,并创建global_table,branch_table,lock_table三张表,点击这里下载。

      -- -------------------------------- The script used when storeMode is 'db' --------------------------------
      -- the table to store GlobalSession data
      CREATE TABLE IF NOT EXISTS `global_table`
      (
          `xid`                       VARCHAR(128) NOT NULL,
          `transaction_id`            BIGINT,
          `status`                    TINYINT      NOT NULL,
          `application_id`            VARCHAR(32),
          `transaction_service_group` VARCHAR(32),
          `transaction_name`          VARCHAR(128),
          `timeout`                   INT,
          `begin_time`                BIGINT,
          `application_data`          VARCHAR(2000),
          `gmt_create`                DATETIME,
          `gmt_modified`              DATETIME,
          PRIMARY KEY (`xid`),
          KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
          KEY `idx_transaction_id` (`transaction_id`)
      ) ENGINE = InnoDB
        DEFAULT CHARSET = utf8;
      
      -- the table to store BranchSession data
      CREATE TABLE IF NOT EXISTS `branch_table`
      (
          `branch_id`         BIGINT       NOT NULL,
          `xid`               VARCHAR(128) NOT NULL,
          `transaction_id`    BIGINT,
          `resource_group_id` VARCHAR(32),
          `resource_id`       VARCHAR(256),
          `branch_type`       VARCHAR(8),
          `status`            TINYINT,
          `client_id`         VARCHAR(64),
          `application_data`  VARCHAR(2000),
          `gmt_create`        DATETIME(6),
          `gmt_modified`      DATETIME(6),
          PRIMARY KEY (`branch_id`),
          KEY `idx_xid` (`xid`)
      ) ENGINE = InnoDB
        DEFAULT CHARSET = utf8;
      
      -- the table to store lock data
      CREATE TABLE IF NOT EXISTS `lock_table`
      (
          `row_key`        VARCHAR(128) NOT NULL,
          `xid`            VARCHAR(96),
          `transaction_id` BIGINT,
          `branch_id`      BIGINT       NOT NULL,
          `resource_id`    VARCHAR(256),
          `table_name`     VARCHAR(32),
          `pk`             VARCHAR(36),
          `gmt_create`     DATETIME,
          `gmt_modified`   DATETIME,
          PRIMARY KEY (`row_key`),
          KEY `idx_branch_id` (`branch_id`)
      ) ENGINE = InnoDB
        DEFAULT CHARSET = utf8;
      
    • 使⽤nacos-config.sh ⽤于向 Nacos 中添加配置

      #!/usr/bin/env bash
      # Copyright 1999-2019 Seata.io Group.
      #
      # Licensed under the Apache License, Version 2.0 (the "License");
      # you may not use this file except in compliance with the License.
      # You may obtain a copy of the License at、
      #
      #      http://www.apache.org/licenses/LICENSE-2.0
      #
      # Unless required by applicable law or agreed to in writing, software
      # distributed under the License is distributed on an "AS IS" BASIS,
      # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      # See the License for the specific language governing permissions and
      # limitations under the License.
      
      while getopts ":h:p:g:t:u:w:" opt
      do
        case $opt in
        h)
          host=$OPTARG
          ;;
        p)
          port=$OPTARG
          ;;
        g)
          group=$OPTARG
          ;;
        t)
          tenant=$OPTARG
          ;;
        u)
          username=$OPTARG
          ;;
        w)
          password=$OPTARG
          ;;
        ?)
          echo " USAGE OPTION: $0 [-h host] [-p port] [-g group] [-t tenant] [-u username] [-w password] "
          exit 1
          ;;
        esac
      done
      
      if [[ -z ${host} ]]; then
          host=localhost
      fi
      if [[ -z ${port} ]]; then
          port=8848
      fi
      if [[ -z ${group} ]]; then
          group="SEATA_GROUP"
      fi
      if [[ -z ${tenant} ]]; then
          tenant=""
      fi
      if [[ -z ${username} ]]; then
          username=""
      fi
      if [[ -z ${password} ]]; then
          password=""
      fi
      
      nacosAddr=$host:$port
      contentType="content-type:application/json;charset=UTF-8"
      
      echo "set nacosAddr=$nacosAddr"
      echo "set group=$group"
      
      failCount=0
      tempLog=$(mktemp -u)
      function addConfig() {
        curl -X POST -H "${contentType}" "http://$nacosAddr/nacos/v1/cs/configs?dataId=$1&group=$group&content=$2&tenant=$tenant&username=$username&password=$password" >"${tempLog}" 2>/dev/null
        if [[ -z $(cat "${tempLog}") ]]; then
          echo " Please check the cluster status. "
          exit 1
        fi
        if [[ $(cat "${tempLog}") =~ "true" ]]; then
          echo "Set $1=$2 successfully "
        else
          echo "Set $1=$2 failure "
          (( failCount++ ))
        fi
      }
      
      count=0
      for line in $(cat $(dirname "$PWD")/config.txt | sed s/[[:space:]]//g); do
        (( count++ ))
      	key=${line%%=*}
          value=${line#*=}
      	addConfig "${key}" "${value}"
      done
      
      echo "========================================================================="
      echo " Complete initialization parameters,  total-count:$count ,  failure-count:$failCount "
      echo "========================================================================="
      
      if [[ ${failCount} -eq 0 ]]; then
      	echo " Init nacos config finished, please start seata-server. "
      else
      	echo " init nacos config fail. "
      fi
      
    • 将nacos-config.sh放在D:seataconf⽂件夹中

      一篇入门分布式事务框架Seata【AT模式】第4张

    • 打开git bash here 执⾏nacos-config.sh,需要提前将nacos启动

      sh nacos-config.sh -h 127.0.0.1 -p 8848 -g SEATA_GROUP -t b8107264-b340-4939-8d60-df77d18057dc
      

      一篇入门分布式事务框架Seata【AT模式】第5张

      看下nacos中配置列表
      一篇入门分布式事务框架Seata【AT模式】第6张

  3. 启动Seata Server

    一篇入门分布式事务框架Seata【AT模式】第7张

    查看控制台中打印信息,启动成功!

    一篇入门分布式事务框架Seata【AT模式】第8张

    观察nacos服务列表

    一篇入门分布式事务框架Seata【AT模式】第9张

TM/RM整合Seata

  1. 数据库环境准备

    ​ 新建工程study-seata,分为业务聚合(TM)study-seata-business,订单服务(RM)study-seata-order,积分服务(RM)study-seata-point,库存服务(RM)study-seata-storage。

    ​ 新建seata_order数据库,新建表t_order:

    CREATE TABLE `t_order`  (
      `id` bigint(20) NOT NULL COMMENT '订单id',
      `goods_Id` int(11) NULL DEFAULT NULL COMMENT '商品ID',
      `num` int(11) NULL DEFAULT NULL COMMENT '商品数量',
      `money` decimal(10, 0) NULL DEFAULT NULL COMMENT '商品总金额',
      `create_time` datetime(0) NULL DEFAULT NULL COMMENT '订单创建时间',
      `username` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '用户名称',
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8;
    

    ​ 新建seata_point数据库,新建表t_point:

    CREATE TABLE `t_point`  (
      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '积分ID',
      `username` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '用户名',
      `point` int(11) NULL DEFAULT NULL COMMENT '用户积分',
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8;
    

    ​ 新建seata_storage数据库,新建表t_storage:

    CREATE TABLE `t_storage`  (
      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '库存ID',
      `goods_id` int(11) NULL DEFAULT NULL COMMENT '商品ID',
      `storage` int(11) NULL DEFAULT NULL COMMENT '库存量',
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8;
    
    INSERT INTO `t_storage` VALUES (1, 1, 100);
    

    ​ AT 模式在RM端需要 UNDO_LOG 表,来记录每个RM的事务信息,主要包含数据修改前后的相关信息,⽤于回滚处理,所以在所有数据库中分别执⾏:

    CREATE TABLE `undo_log` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `branch_id` bigint(20) NOT NULL,
      `xid` varchar(100) NOT NULL,
      `context` varchar(128) NOT NULL,
      `rollback_info` longblob NOT NULL,
      `log_status` int(11) NOT NULL,
      `log_created` datetime NOT NULL,
      `log_modified` datetime NOT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
    

    ​ 最终数据库环境搭建好了,如下:

    一篇入门分布式事务框架Seata【AT模式】第10张

  2. 项目依赖配置

    版本选择:

    工具/服务组件版本
    JDK1.8
    SpringBoot2.3.2.RELEASE
    SpringCloudHoxton.SR8
    Spring-Cloud-alibaba2.2.5.RELEASE
    Seata1.4.0
    Mysql5.7.28

    父pom.xml中添加Seata依赖管理,⽤于Seata的版本锁定。

    <dependencyManagement>
        <dependencies>
            <!--Spring Cloud-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR8</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--Spring Cloud Alibaba-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.5.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--mysql -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.47</version>
            </dependency>
            <!-- seata-spring-boot-starter -->
            <dependency>
                <groupId>io.seata</groupId>
                <artifactId>seata-spring-boot-starter</artifactId>
                <version>1.4.0</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    

    在TM/RM端pom.xml中引入Seata依赖:

    <!--seata依赖-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        <!--排除低版本seata依赖-->
        <exclusions>
            <exclusion>
                <groupId>io.seata</groupId>
                <artifactId>seata-spring-boot-starter</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!--添加⾼版本seata依赖-->
    <dependency>
        <groupId>io.seata</groupId>
        <artifactId>seata-spring-boot-starter</artifactId>
        <version>1.4.0</version>
    </dependency>
    

    关于依赖引入请参考版本说明

  3. 微服务配置

    study-seata-business的application.yml配置如下:

    server:
      port: 8080
    spring:
      application:
        name: study-seata-business
      # nacos配置
      cloud:
        nacos:
          # 服务发现
          discovery:
            server-addr: 127.0.0.1:8848
            namespace: b8107264-b340-4939-8d60-df77d18057dc
            username: nacos
            password: nacos
    # seata配置
    seata:
      tx-service-group: my_tx_group
      registry:
        type: Nacos
        nacos:
          application: seata-server
          server-addr: 127.0.0.1:8848
          group: SEATA_GROUP
          namespace: b8107264-b340-4939-8d60-df77d18057dc
          username: nacos
          password: nacos
      config:
        type: Nacos
        nacos:
          server-addr: 127.0.0.1:8848
          group: SEATA_GROUP
          namespace: b8107264-b340-4939-8d60-df77d18057dc
          username: nacos
          password: nacos
    # ribbon配置
    ribbon:
      ReadTimeout: 500000
      ConnectTimeout: 50000
    

    study-seata-service-order的application.yml配置如下:

    server:
      port: 8081
    spring:
      application:
        name: study-seata-service-order
      # 数据源配置
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/seata_order?useSSL=false
        username: root
        password: 123456
      # nacos配置
      cloud:
        nacos:
          # 服务发现
          discovery:
            server-addr: 127.0.0.1:8848
            namespace: b8107264-b340-4939-8d60-df77d18057dc
            username: nacos
            password: nacos
    # seata配置
    seata:
      tx-service-group: my_tx_group
      registry:
        type: Nacos
        nacos:
          application: seata-server
          server-addr: 127.0.0.1:8848
          group: SEATA_GROUP
          namespace: b8107264-b340-4939-8d60-df77d18057dc
          username: nacos
          password: nacos
      config:
        type: Nacos
        nacos:
          server-addr: 127.0.0.1:8848
          group: SEATA_GROUP
          namespace: b8107264-b340-4939-8d60-df77d18057dc
          username: nacos
          password: nacos
    

    study-seata-service-point的application.yml配置如下:

    server:
      port: 8082
    spring:
      application:
        name: study-seata-service-point
      # 数据源配置
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/seata_point?useSSL=false
        username: root
        password: 123456
      # nacos配置
      cloud:
        nacos:
          # 服务发现
          discovery:
            server-addr: 127.0.0.1:8848
            namespace: b8107264-b340-4939-8d60-df77d18057dc
            username: nacos
            password: nacos
    # seata配置
    seata:
      tx-service-group: my_tx_group
      registry:
        type: Nacos
        nacos:
          application: seata-server
          server-addr: 127.0.0.1:8848
          group: SEATA_GROUP
          namespace: b8107264-b340-4939-8d60-df77d18057dc
          username: nacos
          password: nacos
      config:
        type: Nacos
        nacos:
          server-addr: 127.0.0.1:8848
          group: SEATA_GROUP
          namespace: b8107264-b340-4939-8d60-df77d18057dc
          username: nacos
          password: nacos
    

    study-seata-service-storage的application.yml配置如下:

    server:
      port: 8083
    spring:
      application:
        name: study-seata-service-storage
      # 数据源配置
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/seata_storage?useSSL=false
        username: root
        password: 123456
      # nacos配置
      cloud:
        nacos:
          # 服务发现
          discovery:
            server-addr: 127.0.0.1:8848
            namespace: b8107264-b340-4939-8d60-df77d18057dc
            username: nacos
            password: nacos
    # seata配置
    seata:
      tx-service-group: my_tx_group
      registry:
        type: Nacos
        nacos:
          application: seata-server
          server-addr: 127.0.0.1:8848
          group: SEATA_GROUP
          namespace: b8107264-b340-4939-8d60-df77d18057dc
          username: nacos
          password: nacos
      config:
        type: Nacos
        nacos:
          server-addr: 127.0.0.1:8848
          group: SEATA_GROUP
          namespace: b8107264-b340-4939-8d60-df77d18057dc
          username: nacos
          password: nacos
    
  4. 添加注解@GlobalTransactional

    AT模式实现分布式事务十分简单,在发起全局事务的TM端方法上增加@GlobalTransactional就可以了。

    先看看不加此注解时各服务的操作DB的情况:

    一篇入门分布式事务框架Seata【AT模式】第11张

    此时,并未加注解@GlobalTransactional,在碰到异常时,订单表插入了一行,积分增加了,但是库存并没有减少:

    一篇入门分布式事务框架Seata【AT模式】第12张

    一篇入门分布式事务框架Seata【AT模式】第13张

    一篇入门分布式事务框架Seata【AT模式】第14张

    一篇入门分布式事务框架Seata【AT模式】第15张

    现在,加上注解@GlobalTransactional,再次观察一下这三个表中的变化,最好打一下断点,你会发现,程序在执行完异常代码处,会回滚掉插入的订单和被修改的积分值,说明保证了分布式事务中各分支事务要么都成功提交要么发生异常都回滚。

总结

​ AT 模式是⼀种⽆侵⼊的分布式事务解决⽅案。在 AT 模式下,⽤户只需关注⾃⼰的“业务 SQL”,⽤户的 “业务 SQL” 作为⼀阶段,Seata 框架会⾃动⽣成事务的⼆阶段提交和回滚操作。AT模式基于数据源代理实现,对ORM框架十分友好,集成难度低,学习成本低,十分推荐使用。

​ 最后,附上源码

免责声明:文章转载自《一篇入门分布式事务框架Seata【AT模式】》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇使用HackRF+GNU Radio 破解吉普车钥匙信号C# 互操作性入门系列(二):使用平台调用调用Win32 函数下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Android 如何动态添加 View 并显示在指定位置。

引子 最近,在做产品的需求的时候,遇到 PM 要求在某个按钮上添加一个新手引导动画,引导用户去点击。作为 RD,我哗啦啦的就写好相关逻辑了。自测完成后,提测,PM Review 效果。 看完后,PM 提了个问题,这个动画效果范围能不能再大一点?PM 解释到按钮本身大小不是很大,会导致引导效果不够明显,也会导致用户的点击欲望不够。我想了想,似乎很有道理啊,但...

ElementUI中的el-table怎样实现每一列显示的是控件并能动态实现双向数据绑定

场景 要实现在ElementUI的表格中每一列展示的不是数据而是控件。效果如下 注: 博客: https://blog.csdn.net/badao_liumang_qizhi关注公众号 霸道的程序猿 获取编程相关电子书、教程推送与免费下载。 实现 普通表格官方示例代码 <template> <el-table :data...

CVE-2020-14825:Weblogic反序列化漏洞复现

全程无图,全靠编 参考:https://mp.weixin.qq.com/s?__biz=MzA4NzUwMzc3NQ==&mid=2247486336&idx=1&sn=2a054ededbc855622fe2ac6c8906aae0&chksm=90392d70a74ea46635bef3cd4fc414cef87d16...

阻止Bootstrap 模态框点击背景空白处自动关闭

问题描述 模态框点击空白处,会自动关闭,怎么阻止关闭事件呢? 解决方法 在HTML页面中编写模态框时,在div初始化时添加属性 aria-hidden=”true” data-backdrop=”static”,即可。 <!-- 模态框(Modal) --> <div class="modal fade" id="myModal" t...

WPF数据绑定(四)

1、DataTemplate 上一部分已经讲了itemsource绑定,功能虽然实现了但是还是有点土,内容太单一了,如果能够修改listbox的界面,让更多的元素展示出来就完美了。 DataTemplate就可以实现这个。 1 <TabItem Header="DataTemplate"> 2...

WebService(axis2),整合springmvc

 1.导入jar 2.在spring.xml中添加 <!-- axis2start --> <bean id="testWSService" class="com.sh.test.WsTestServer"></bean> <bean id="testWSService1" class="com....