• [问题求助] linux发布之后找不到数据库
    开发是用windows开发的,发版是Linux环境,Windows环境发版没有问题【主库】Next recovery time: 2024/1/15 17:50:50 (ERROR [01000] [unixODBC][Driver Manager]Can't open lib '/usr/lib64/psqlodbcw.so' : file not found)【主库】Next recovery time: 2024/1/15 17:41:38 (ERROR [01000] [unixODBC][Driver Manager]Can't open lib 'PostgreSQL Unicode' : file not found)
  • [技术干货] ShardingSphere水平分表策略配置和测试实战
    水平分表 把一个表的数据分到一个数据库的多张表中,每个表只有这个表的部分数据 核心是把一个大表,分割N个小表,每个表的结构是一样的,数据不一样,全部表的数据合起来就是全部数据 针对数据量巨大的单张表(比如订单表),按照某种规则(RANGE,HASH取模等),切分到多张表里面去 但是这些表还是在同一个库中,所以单数据库操作还是有IO瓶颈,主要是解决单表数据量过大的问题 减少锁表时间,没分表前,如果是DDL(create/alter/add等)语句,当需要添加一列的时候mysql会锁表,期间所有的读写操作只能等待 水平分表的适用场景 当一张表的数据达到几千万时,查询一次所花的时间长,需要进行优化,缩短查询时间 微博发送记录、微信消息记录、日志记录。以id增长或时间划分 网站签到等活动流水数据。以时间划分 依赖引入  <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-spring-boot-starter</artifactId> <version>4.1.1</version> </dependency>application# 数据源 ds0 第一个数据库 --- 版本:mysql8 shardingsphere: datasource: ds0: connectionTimeoutMilliseconds: 30000 driver-class-name: com.mysql.cj.jdbc.Driver idleTimeoutMilliseconds: 60000 jdbc-url: jdbc:mysql://[ip]:3306/[数据库]?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true maintenanceIntervalMilliseconds: 30000 maxLifetimeMilliseconds: 1800000 maxPoolSize: 50 minPoolSize: 50 password: [密码] type: com.zaxxer.hikari.HikariDataSource username: [用户名] names: ds0 props: # 打印执行的数据库以及语句 sql: show: true sharding: tables: [表名]: # 指定表的数据分布情况,配置数据节点,行表达式标识符使用 ${...} 或 $->{...},但前者与 Spring 本身的文件占位符冲突,所以在 Spring 环境中建议使用 $->{...} actual-data-nodes: ds0.[表名_]$->{0..1} # 水平分表策略+行表达式分片 table-strategy: inline: algorithm-expression: [表名_]$->{[取模字段] % 2} sharding-column: [取模字段] #id生成策略 key-generator: column: id props: worker: id: 0 #id生成策略 type: SNOWFLAKE 测试 @Test public void testSaveTraffic(){ Random random = new Random(); for(int i=0;i<10;i++){ TrafficDO trafficDO = new TrafficDO(); // 设置取模字段的值 Int trafficDO.setAccountNo(Long.valueOf(random.nextInt(1000))); trafficMapper.insert(trafficDO); } } 结果分析 取模字段accountNo为偶数的对象,存储到traffic_0表 取模字段accountNo为奇数的对象,存储到traffic_1表 实现水平分表 转载自https://www.cnblogs.com/xietingwei/p/17571979.html
  • [技术干货] 业界常见分库分表中间件
    常见的分库分表中间件包括ShardingSphere、MyCAT、Vitess和TDDL。以下是它们的优缺点: ShardingSphere: 优点: 功能全面:提供了完整的分库分表解决方案,支持多种数据库和丰富的功能,如读写分离、分布式事务等。 社区活跃:由Apache孵化,具有庞大的开源社区支持,更新迭代速度快。 易于集成:提供了丰富的文档和示例,易于使用和集成到现有项目中。 缺点: 配置复杂:相对其他中间件而言,配置和部署可能较为繁琐,需要一定的学习成本。 MyCAT: 优点: 简单易用:相对较简单的配置和部署,容易上手和使用。 支持MySQL协议:兼容MySQL协议,应用程序无需修改即可访问分片和分表数据。 缺点: 社区活跃度较低:相比其他几个中间件,MyCAT的社区活跃度较低,更新和维护可能相对滞后。 功能相对较少:相对于一些更全面的中间件,MyCAT的功能相对有限,可能不适用于更复杂的场景。 Vitess: 优点: 高度可扩展:通过分片实现数据的水平扩展,能够轻松应对大规模数据和高并发访问。 负载均衡和故障恢复:提供了自动负载均衡和故障恢复功能,提高了系统的可靠性和可用性。 缺点: 学习曲线较陡峭:配置和使用Vitess需要一定的学习成本,上手和部署可能相对复杂。 对MySQL兼容性有限:由于Vitess是为MySQL设计的,与其他数据库的兼容性相对较差。 TDDL: 优点: 阿里巴巴支持:由阿里巴巴集团开发和维护,具有较强的技术实力和稳定性。 动态数据切换:支持动态数据切换,方便在运行时进行数据分片和迁移。 缺点: 社区支持相对较少:与一些更受欢迎的开源中间件相比,TDDL的社区支持相对较少。 选择适合的分库分表中间件需要根据具体的需求和场景来评估。您需要考虑到中间件的功能覆盖、易用性、社区支持、稳定性和对应数据库的兼容性等因素,并结合自身团队的技术实力和偏好进行选择。 
  • 通过jdbc提交mrs作业到yarn上,mrs是否接口可以返回yarn上执行的作业占用的资源(cpu/memory)的情况
    请教个问题,通过jdbc提交mrs作业到yarn上,mrs是否接口可以返回yarn上执行的作业占用的资源(cpu/memory)的情况
  • [其他] SpringBoot使用Sharding-JDBC实现读写分离
    SpringBoot使用Sharding-JDBC实现读写分离在大数据时代,数据库的性能问题越来越受到关注。为了提高系统的性能,我们可以通过读写分离的方式来优化数据库的访问。本文将介绍如何在SpringBoot和MyBatis项目中使用Sharding-JDBC实现读写分离。一、环境准备安装Java开发环境(JDK 1.8或更高版本)。安装Maven构建工具。安装MySQL数据库。下载并安装Sharding-JDBC。二、配置SpringBoot在pom.xml文件中添加Sharding-JDBC依赖:<dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-spring-boot-starter</artifactId> <version>4.1.1</version> </dependency>在application.yml文件中配置数据源和分片规则:spring: shardingsphere: datasource: names: ds0,ds1 ds0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/ds0?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8 username: root password: 123456 ds1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/ds1?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8 username: root password: 123456 sharding: default-database-strategy: inline: sharding-column: id algorithm-expression: ds${id % 2} tables: t_order: actual-data-nodes: ds${0..1}.t_order${0..1} table-strategy: standard: sharding-column: order_id sharding-algorithm-name: inline在src/main/resources目录下创建sql文件夹,并在其中创建两个t_order_${0..1}.sql文件,分别用于模拟ds0和ds1的数据表结构。例如,ds0的数据表结构如下:CREATE TABLE `t_order` ( `id` int(11) NOT NULL, `order_id` int(11) NOT NULL, `create_time` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;在Service类中注入DataSource,并使用@Autowired注解。例如,在一个Service类中:import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.zaxxer.hikari.HikariDataSource; import org.springframework.transaction.annotation.Transactional; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.namedparam JdbcTemplate; // import语句错误,请自行解决 @Service public class OrderService extends ServiceImpl<OrderMapper, Order> { @Autowired private HikariDataSource ds0; @Autowired private HikariDataSource ds1; @Transactional(rollbackFor = Exception.class) public void createOrder(Order order) { // 根据order的id判断应该使用哪个数据源进行插入操作 if (order.getId() % 2 == 0) { jdbcTemplate(ds0).insert(order); } else { jdbcTemplate(ds1).insert(order); } } }四、总结通过本文的介绍,我们了解了如何在SpringBoot和MyBatis项目中使用Sharding-JDBC实现读写分离。具体来说,我们需要完成以下几个步骤:在pom.xml文件中添加Sharding-JDBC依赖。在application.yml文件中配置数据源和分片规则。在Service类中注入DataSource,并使用@Autowired注解。根据order的id判断应该使用哪个数据源进行插入操作。通过以上步骤,我们就可以实现在SpringBoot和MyBatis项目中使用Sharding-JDBC进行读写分离。需要注意的是,在使用Sharding-JDBC时,我们需要根据具体的业务需求来选择合适的分片策略和数据源。同时,我们也需要对数据库进行合理的优化,以提高系统的性能。
  • [技术干货] JDBC连接相关jar包获取及上传管理中心白名单处理
    jar包获取以下提供的都是各个数据库较为官方的jar包获取方式1、Mysqlcid:link_5 tar.gz为Linux系统的压缩包,zip为Windows系统的压缩包 在下载好的zip压缩包中包含有jar包文件,解压出来使用即可2、MariaDBcid:link_4 点击 Download MariaDB Connector/J 按钮跳转 选择Connectors,Connector version选择MariaDB Connector/J 3.1.0,镜像地址可以切换,选择一个能下载的即可3、Oraclecid:link_1 根据Oracle服务器的版本选择对应的驱动版本下载即可4、PostgreSQLcid:link_7 选择合适的版本点击下载即可5、MongoDBcid:link_3选择合适的jar包驱动版本 选择jar格式的下载包6、SQL Servercid:link_0 tar.gz格式的压缩包适合在Linux系统解压,zip格式的压缩包适合在Windows系统解压,下载此驱动程序时,有多个 JAR 文件。 JAR 文件名表示它支持的 Java 版本,选择合适的版本使用详细操作可以参考本论坛的这个帖子 cid:link_27、SQLitecid:link_6 选择合适的版本点击Downloads按钮 在Assets项下选择jar格式的文件点击下载管理中心白名单处理如果项目中包含jar包文件,在管理中心上传脚本时会触发文件类型的白名单检查,如果未配置jar包中的相关文件类型,那么就无法通过白名单检查,从而管理中心上传脚本失败。jar包文件本质上是一个压缩包,白名单检查会校验压缩包中所有文件的类型,所以我们需要将压缩包中所有的文件类型填写到白名单中。使用解压缩软件就可以解压查看jar包中的文件类型信息了,这里以7-Zip软件示例。 jar包中包含的文件可能比较多,要查找所有的文件类型信息比较耗时,我这里总结了前面数据库的jar包中包含文件的类型,各个类型通过分号(;)分割,这也是管理中心白名单配置的格式。jar;class;MF;LIST;Driver;properties;xml;AuthenticationPlugin;Codec;CredentialPlugin;TlsSocketPlugin;RSA;SF;txt;json;glb;so;zentus;dll;jnilib;以上整理的jar包中的文件类型可能随版本变动而变动,或者你拥有的jar包没在这个整理范围,所以建议你自己解压jar包统计一下相关的文件类型。如果觉得文件类型过多,难以统计,可以选择在文件类型的白名单中添加星号(*)来达到允许所有文件类型的设置,但是这样就放开了文件类型的风险管控,需要自己评估相关操作的风险系数,建议谨慎操作。
  • [常见问题汇总帖] 使用JDBC连接opengauss密态数据库时,出现no gauss_cl_jni in java.library.path的报错
    如图,想问下要怎么解决。选择的jar包是:gsjdbc4.jar而且很奇怪的是,没有设置enable_ce=1时,就能成功连接,设置后才会出现报错。
  • [集群异常] copy报错connection failed when canceling copy operation
    【问题现象】jdbc copy报错connection failed when canceling copy operation【问题原因】copy过程中出现错误导致copy失败【排查过程】1. 首先测试jdbc客户端到DWS网络是否正常,jdbc是否能建立稳定连接2. 排查DWS CN日志,是否有copy相关报错3. 本例中,是由于copy的目标表上有not null约束,但导入的数据包含null值,导致copy报错,由于使用的开源驱动,错误未直接在客户端显示【解决方法】根据CN日志报错内容,去除目标表的not null约束
  • [技术干货] openGauss数据库JDBC环境连接配置(Eclipse)
    测试环境 客户端系统: Windows 10 客户端软件: eclipse 2020-09 Server操作系统:openEuler 20.03 64bit with ARM Database版本: openGauss 2.0.0 作者:酷哥1.客户端安装配置JDK11 DOS窗口输入“java -version”,查看JDK版本,确认为JDK11版本。如果未安装JDK,请 从官方网站下载安装包并安装。 根据如下步骤配置系统环境变量: a. 右键单击“我的电脑“,选择“属性“。 b. 在“系统“页面左侧导航栏单击“高级系统设置“。 c. 在“系统属性“页面,“高级“页签上单击“环境变量“。 d. 在“环境变量“页面上,“系统变量“区域单击“新建“或“编辑“配置系统变量。变量说明请参 见表。2.下载JDBC驱动并解压 下载地址:https://opengauss.obs.cn-south-1.myhuaweicloud.com/2.0.0/x86/openGauss-2.0.0-JDBC.tar.gz 3.启动eclipse,新建工程并添加JDBC驱动 Create a java projectProject name: openGauss-JDBC; JRE: JavaSE-11不需要创建“Don’t Create”创建一个lib目录在openGauss-JDBC项目下把jdbc驱动拷贝到lib下边加载jdbc驱动“Add JARs”在“Libraries”下,选中需要的postgresql.jar文件,然后“Apply and Close”Jdbc jar已经被正确加载,在“Referenced Libraries”下创建“Java Class”拷贝准备的代码到java类中运行java类“Run as --》java application”Tips: 此次使用eclipse 2020-09 创建JAVA Class/*测试代码*/ package gaussjdbc; //ogtest.java //演示基于JDBC开发的主要步骤,会涉及创建数据库、创建表、插入数据等。 import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; import java.sql.CallableStatement; public class Gaussjdbc { //创建数据库连接。 public static Connection GetConnection(String username, String passwd) { String driver = "org.postgresql.Driver"; String sourceURL = "jdbc:postgresql://122.9.34.186:26000/db_tpcc"; Connection conn = null; try { //加载数据库驱动。 Class.forName(driver).newInstance(); } catch (Exception e) { e.printStackTrace(); return null; } try { //创建数据库连接。 conn = DriverManager.getConnection(sourceURL, username, passwd); System.out.println("Connection succeed!"); } catch (Exception e) { e.printStackTrace(); return null; } return conn; }; //执行普通SQL语句,创建customer_t1表。 public static void CreateTable(Connection conn) { Statement stmt = null; try { stmt = conn.createStatement(); //执行普通SQL语句。 int rc = stmt .executeUpdate("CREATE TABLE customer_t1(c_customer_sk INTEGER, c_customer_name VARCHAR(32));"); stmt.close(); } catch (SQLException e) { if (stmt != null) { try { stmt.close(); } catch (SQLException e1) { e1.printStackTrace(); } } e.printStackTrace(); } } //执行预处理语句,批量插入数据。 public static void BatchInsertData(Connection conn) { PreparedStatement pst = null; try { //生成预处理语句。 pst = conn.prepareStatement("INSERT INTO customer_t1 VALUES (?,?)"); for (int i = 0; i < 3; i++) { //添加参数。 pst.setInt(1, i); pst.setString(2, "data " + i); pst.addBatch(); } //执行批处理。 pst.executeBatch(); pst.close(); } catch (SQLException e) { if (pst != null) { try { pst.close(); } catch (SQLException e1) { e1.printStackTrace(); } } e.printStackTrace(); } } //执行预编译语句,更新数据。 public static void ExecPreparedSQL(Connection conn) { PreparedStatement pstmt = null; try { pstmt = conn .prepareStatement("UPDATE customer_t1 SET c_customer_name = ? WHERE c_customer_sk = 1"); pstmt.setString(1, "new Data"); int rowcount = pstmt.executeUpdate(); pstmt.close(); } catch (SQLException e) { if (pstmt != null) { try { pstmt.close(); } catch (SQLException e1) { e1.printStackTrace(); } } e.printStackTrace(); } } /** * 主程序,逐步调用各静态方法。 * @param args */ public static void main(String[] args) { //创建数据库连接。 Connection conn = GetConnection("joe", "Bigdata@123"); //创建表。 CreateTable(conn); //批插数据。 BatchInsertData(conn); //执行预编译语句,更新数据。 ExecPreparedSQL(conn); //关闭数据库连接。 try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }4.测试示例代码5.检查运行结果 -- 检查客户端运行结果 --检查数据库数据变化 代码成功运行,且数据库数据变更正常,即连接环境配置完毕。
  • [技术干货] 使用JDBC连接数据库
    操作步骤1. 准备连接环境步骤1  修改数据库的pg_hba.conf文件。在GS_HOME中查找pg_hba.conf文件,本实验中数据库GS_HOME设置的为/gaussdb/data/db1,实际操作中GS_HOME地址可以查看安装时的配置文件:<PARAM name="dataNode1" value="/gaussdb/data/db1"/>。cd /gaussdb/data/db1 vi pg_hba.conf输入“:90”找到对应位置,然后输入“i”切换到INSERT模式,将以下内容添加进pg_hba.conf文件,添加后按下“ESC”键,退出INSERT模式,输入“:wq”后回车保存。# IPv4 local connections: host all all 127.0.0.1/32 trust host all all 192.168.0.19/32 trust host all all 0.0.0.0/0 sha256 # IPv6 local connections: host all all ::1/128 trust使用omm用户登陆,使用gs_ctl将策略生效。su - omm gs_ctl reload -D /gaussdb/data/db1/返回结果为:[2020-07-23 15:39:55.398][71828][][gs_ctl]: gs_ctl reload ,datadir is -D "/gaussdb/data/db1" server signaled注:如果之前没有启动过数据库,返回结果如下,继续操作即可:[2021-04-27 17:02:59.640][15354][][gs_ctl]: gs_ctl reload ,datadir is /gaussdb/data/db1 [2021-04-27 17:02:59.640][15354][][gs_ctl]: PID file "/gaussdb/data/db1/postmaster.pid" does not exist [2021-04-27 17:02:59.640][15354][][gs_ctl]: Is server running?步骤2 使用omm用户登陆数据库给dbuser用户授权,并退出数据库。gsql -d postgres -p 26000 -r postgres=# alter role dbuser createrole createdb; ALTER ROLE postgres=# \q步骤3 修改数据库侦听地址。在GS_HOME中,本实验中数据库GS_HOME设置的为/gaussdb/data/db1。cd /gaussdb/data/db1 vi postgresql.conf输入“:60”找到对应位置,然后输入“i”切换到INSERT模式,将listen_addresses的值修改成为*,修改后按下“ESC”键,退出INSERT模式,输入“:wq”后回车保存。#listen_addresses = '192.168.0.19' # what IP address(es) to listen on; listen_addresses = '*'修改完成后重启数据库生效(-D后面的数据库默认路径,需要根据实际情况进行修改)。gs_ctl restart -D /gaussdb/data/db1/步骤4 下载Java连接openGauss的驱动包,并将其导入对应的使用工具。通过以下链接,下载驱动包https://opengauss.obs.cn-south-1.myhuaweicloud.com/1.1.0/arm/openGauss-1.1.0-JDBC.tar.gz假设文件存放在d:\Download目录下,并进行解压,解压后文件为“postgresql.jar”。步骤5 创建测试数据库demo。使用gsql工具登陆数据库,并输入dbuser密码(如:Gauss#3demo)。gsql -d postgres -p 26000 -U dbuser -r创建数据库demo。create database demo ENCODING 'UTF8' template = template0;切换到demo数据库,并输入dbuser密码(如:Gauss#3demo)。\connect demo;步骤6 创建名为demo的schema,并设置demo为当前的schema。CREATE SCHEMA demo;将默认搜索路径设为demo。SET search_path TO demo;步骤7 创建测试表websites。CREATE TABLE websites ( id int NOT NULL, name char(20) NOT NULL DEFAULT '', url varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (id) ); COMMENT ON COLUMN websites.name IS '站点名称';步骤8 插入数据。INSERT INTO websites VALUES ('1', 'openGauss', 'https://opengauss.org/zh/'), ('2', '华为云', 'https://www.huaweicloud.com/'), ('3', 'openEuler', 'https://openeuler.org/zh/'), ('4', '华为support中心', 'https://support.huaweicloud.com/');步骤9 退出数据库。\q2. 确定26000端口是否放开步骤1 打开华为云首页,登录后进入“控制台”,点击“弹性云服务器ECS”进入ECS列表。步骤2 在云服务器控制台找到安装数据库主机的ECS,点击查看基本信息,找到安全组。步骤3 点击进入安全组,选择“入方向规则”并“添加规则”,进行26000端口设置。确定后,可以看到入网规则多了“TCP:26000”,如下图:3. 下载并安装JDK 步骤1 下载JDK。https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html 步骤2 点击jdk-8u261-windows-x64.exe进行安装。默认设置即可,出现安装进度。如下显示表示安装成功:步骤3 查看安装目录。4. 配置JDK环境变量步骤1 右击“此电脑”选择“属性”,点击“高级系统设置”。步骤2 点击“环境变量”,新建系统变量“JAVA_HOME”,输入JDK安装目录。“C:\Program Files\Java\jdk1.8.0_261”为JDK安装目录。步骤3 编辑系统变量“path”。在变量值最后输入 %JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;(注意原来Path的变量值末尾有没有;号,如果没有,先输入;号再输入上面的代码)。步骤4 新建系统变量“CLASSPATH”变量,输入“.” 即可。步骤5 系统变量配置完毕,查询检验是否配置成功,运行cmd 输入java -version(java和 -version之间有空格)。C:\Users\xxxxx>java -version如下所示,显示版本信息,则说明安装和配置成功。java version "1.8.0_261" Java(TM) SE Runtime Environment (build 1.8.0_261-b12) Java HotSpot(TM) 64-Bit Server VM (build 25.261-b12, mixed mode)5. 连接openGauss并执行java代码步骤1 使用Java程序连接数据库并进行查询(注:请用户根据实际情况替换红字内容,修改jdbc:postgresql://弹性公网IP:26000/demo中弹性公网IP信息,USER = "dbuser"连接数据库的用户及密码PASS = "Gauss#3demo")。在d:\Download\中创建openGaussDemo.java文件,文件内容如下,注意红字部分要按照实际情况进行替换:import java.sql.*; public class openGaussDemo { static final String JDBC_DRIVER = "org.postgresql.Driver"; static final String DB_URL = "jdbc:postgresql://弹性公网IP:26000/demo?ApplicationName=app1"; // 数据库的用户名与密码,需要根据自己的设置 static final String USER = "dbuser"; static final String PASS = "Gauss#3demo"; public static void main(String[] args) { Connection conn = null; Statement stmt = null; try{ // 注册 JDBC 驱动 Class.forName(JDBC_DRIVER); // 打开链接 System.out.println("连接数据库..."); conn = DriverManager.getConnection(DB_URL,USER,PASS); // 执行查询 System.out.println(" 实例化Statement对象..."); stmt = conn.createStatement(); String sql; sql = "SELECT id, name, url FROM demo.websites"; ResultSet rs = stmt.executeQuery(sql); // 展开结果集数据库 while(rs.next()){ // 通过字段检索 int id = rs.getInt("id"); String name = rs.getString("name"); String url = rs.getString("url"); // 输出数据 System.out.print("ID: " + id); System.out.print(", 站点名称: " + name); System.out.print(", 站点 URL: " + url); System.out.print("\n"); } // 完成后关闭 rs.close(); stmt.close(); conn.close(); }catch(SQLException se){ // 处理 JDBC 错误 se.printStackTrace(); }catch(Exception e){ // 处理 Class.forName 错误 e.printStackTrace(); }finally{ // 关闭资源 try{ if(stmt!=null) stmt.close(); }catch(SQLException se2){ }// 什么都不做 try{ if(conn!=null) conn.close(); }catch(SQLException se){ se.printStackTrace(); } } System.out.println("Goodbye!"); } }步骤2 在安装Java的本机,打开cmd对Java程序编译后执行。在cmd中,进入d:\Download\目录,先对Java程序进行编译(进入Java程序的目录)。D:\Download> javac -encoding utf-8 -cp d:\Download\postgresql.jar openGaussDemo.java再执行以下命令。D:\Download> java -cp .;D:/Download/postgresql.jar openGaussDemo步骤3 执行结果。执行结果如下:连接数据库... 实例化Statement对象... ID: 1, 站点名称: openGauss, 站点 URL: https://opengauss.org/zh/ ID: 2, 站点名称: 华为云, 站点 URL: https://www.huaweicloud.com/ ID: 3, 站点名称: openEuler, 站点 URL: https://openeuler.org/zh/ ID: 4, 站点名称: 华为support中心, 站点 URL: https://support.huaweicloud.com/ Goodbye!
  • [问题求助] 有无JDBC连接数据库的指导文档
    有修改数据库的pg_hba.conf文件和.修改数据库监听地址步骤的。
  • [问题求助] 有无JDBC链接数据库指导文档
    包含修改数据库的pg_hba.conf和修改数据库监听地址步骤的。刚刚学习,找不到什么资料。
  • [开发应用] 【DWS产品】【JDBC驱动数据写入】SparkApp写入数据异常
    【DWS产品】【JDBC驱动数据写入功能】SparkApp写入数据异常【功能模块】GaussDB(DWS)驱动在SparkApp中使用问题【操作步骤&问题现象】1、在同一个DWS库及模式下,现有两张结构相同的表,分别称为表A、表B。其中表A有大约160万行数据2、编写简单的SparkApp,依赖 gsjdbc200.jar(driverClass=com.huawei.gauss200.jdbc.Driver)和 gsjdbc4.jar(driverClass=org.postgresql.Driver)首先使用gsjdbc200.jar作为驱动读取A表数据,得到Dataset实例Dataset ds = sparkSession.read().format("jdbc") .option("url", "jdbc:gaussdb://xx.xxx.xx.xx:8000/dwhm") .option("driver", "com.huawei.gauss200.jdbc.Driver") .option("user", "******") .option("password", "**********") .option("dbtable", "表A") .option("numPartitions", 6) .option("lowerBound", 0) .option("upperBound", 1728454) .option("partitionColumn", "id_column") .option("fetchsize", 1024) .load();然后立即再使用gsjdbc200.jar(driverClass=com.huawei.gauss200.jdbc.Driver)作为驱动,尝试将A表数据写入B表ds.write().format("jdbc") .option("dbtable", "表B") .mode(SaveMode.Overwrite) .option("url", "jdbc:gaussdb://10.2.121.11:8000/dwhm") .option("driver", "com.huawei.gauss200.jdbc.Driver") .option("user", "xxxxxx") .option("password", "**************") .option("truncate", true) .option("batchsize", 500) .save();读取到数据后,在开始执行数据写入task时很快就报出如下异常而失败。java.sql.BatchUpdateException: Batch entry 0 INSERT INTO 表B名称 ("po_process_action_id","tenant_id","po_header_id","po_line_id","po_line_location_id","display_line_num","display_line_location_num","version_num","process_type_code","process_remark","processed_date","process_user_id","process_user_name","object_version_number","creation_date","created_by","last_updated_by","last_update_date","storage_time") VALUES ('1155064','20453','194759',NULL,NULL,NULL,NULL,'1','UPDATE',NULL,'2021-07-11 01:14:21+08','859953','华为接口账号','1','2021-07-11 01:14:21+08','859953','859953','2021-07-11 01:14:21+08','2021-09-27 13:47:06+08') was aborted: ERROR: invalid input syntax for type oid: "" Call getNextException to see other errors in the batch. at com.huawei.gauss200.jdbc.jdbc.BatchResultHandler.handleError(BatchResultHandler.java:152) at com.huawei.gauss200.jdbc.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2584) at com.huawei.gauss200.jdbc.core.v3.QueryExecutorImpl.executeBatch(QueryExecutorImpl.java:575) at com.huawei.gauss200.jdbc.jdbc.PgStatement.executeBatch(PgStatement.java:879) at com.huawei.gauss200.jdbc.jdbc.PgPreparedStatement.executeBatch(PgPreparedStatement.java:1580) at org.apache.spark.sql.execution.datasources.jdbc.JdbcUtils$.savePartition(JdbcUtils.scala:654) at org.apache.spark.sql.execution.datasources.jdbc.JdbcUtils$$anonfun$saveTable$1.apply(JdbcUtils.scala:821) at org.apache.spark.sql.execution.datasources.jdbc.JdbcUtils$$anonfun$saveTable$1.apply(JdbcUtils.scala:821) at org.apache.spark.rdd.RDD$$anonfun$foreachPartition$1$$anonfun$apply$29.apply(RDD.scala:935) at org.apache.spark.rdd.RDD$$anonfun$foreachPartition$1$$anonfun$apply$29.apply(RDD.scala:935) at org.apache.spark.SparkContext$$anonfun$runJob$5.apply(SparkContext.scala:2074) at org.apache.spark.SparkContext$$anonfun$runJob$5.apply(SparkContext.scala:2074) at org.apache.spark.scheduler.ResultTask.runTask(ResultTask.scala:87) at org.apache.spark.scheduler.Task.run(Task.scala:109) at org.apache.spark.executor.Executor$TaskRunner.run(Executor.scala:345) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) Caused by: com.huawei.gauss200.jdbc.util.PSQLException: ERROR: invalid input syntax for type oid: "" at com.huawei.gauss200.jdbc.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2852) at com.huawei.gauss200.jdbc.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2583) ... 16 more表B只是普通的行存表,其中 po_process_action_id 为主键。表内没有名为“oid”的用户定义列。如果换用 gsjdbc4.jar(driverClass=org.postgresql.Driver)作为 write 时的驱动,只相应改动 url、driver 两项option,其它代码不做任何改变,则数据写入可以成功,没有任何问题。使用Spark版本为 2.3.2,程序运行于本地笔记本,原意为简单试一下Spark读写DWS再例如下面这个异常也是同样的问题:
  • [新手课堂] Spring 学习笔记
    Spring介绍Spring 是一个开源框架,是一个分层的 JavaEE 一站式框架。所谓一站式框架是指 Spring 有 JavaEE 开发的每一层解决方案。WEB层:SpringMVCService层:Spring的Bean管理,声明式事务DAO层:Spring的JDBC模板,ORM模板优点:IOC:方便解耦合AOP:对程序进行扩展轻量级框架方便与其他框架整合Spring使用Spring开发包解压后的目录介绍:docs: Spring 开发规范和APIlibs: Spring jar 包和源代码schema: Spring 配置文件的约束DataAccess 用于数据访问,WEB 用于页面显示,核心容器也就是IOC部分。控制反转(IOC)控制反转(Inversion of Control)是指将对象的创建权反转(交给)Spring。使用IOC就需要导入IOC相关的包,也就是上图中核心容器中的几个包:beans,context,core,expression四个包。实现原理传统方式创建对象:UserDAO userDAO=new UserDAO();进一步面向接口编程,可以多态:UserDAO userDAO=new UserDAOImpl();这种方式的缺点是接口和实现类高耦合,切换底层实现类时,需要修改源代码。程序设计应该满足OCP元祖,在尽量不修改程序源代码的基础上对程序进行扩展。此时,可以使用工厂模式:class BeanFactory{    public static UserDAO getUserDAO(){        return new UserDAOImpl();    }}此种方式虽然在接口和实现类之间没有耦合,但是接口和工厂之间存在耦合。使用工厂+反射+配置文件的方式,实现解耦,这也是 Spring 框架 IOC 的底层实现。//xml配置文件//class BeanFactory{    public static Object getBean(String id){        //解析XML        //反射        Class clazz=Class.forName();        return clazz.newInstance();    }}IOC XML 开发在 docs 文件中包含了 xsd-configuration.hmtl 文件。其中定义了 beans schema。    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">    //在此配置bean      调用类:ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml");UserService userService=(UserService)applicationContext.getBean("userService");userService.save();IOC 和 DIDI 指依赖注入,其前提是必须有 IOC 的环境,Spring 管理这个类的时候将类的依赖的属性注入进来。例如,在UserServiceImpl.java中:public class UserServiceImpl implements UserService{private String name;public void setName(String name){this.name=name;}public void save(){System.out.println("save "+name);}}在配置文件中:    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">             测试代码:@Testpublic void demo2(){//创建Spring工厂ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml");UserService userService=(UserService)applicationContext.getBean("userService");userService.save();}运行结果:save tony可以看到,在配置文件中配置的属性,在 Spring 管理该类的时候将其依赖的属性成功进行了设置。如果不使用依赖注入,则无法使用接口,只能使用实现类来进行设置,因为接口中没有该属性。Spring 的工厂类BeanFactory: 老版本的工厂类,在调用getBean()方法时,才会生成类的实例。ApplicationContext: 在加载配置文件的时候,就会将 Spring 管理的类都实例化。有两个实现类:ClassPathXmlApplicationContext: 加载类路径下的配置文件FileSystemXmlApplicationContext: 加载磁盘下的配置文件bean标签配置id: 唯一约束,不能出现特殊字符name: 理论上可以重复,但是开发中最好不要。可以出现特殊字符生命周期:init-method: bean被初始化的时候执行的方法destroy-method: bean被销毁的时候执行的方法作用范围:scope: bean的作用范围,有如下几种,常用的是前两种singleton: 默认使用单例模式创建prototype: 多例request: 在web项目中,spring 创建类后,将其存入到 request 范围中session: 在web项目中,spring 创建类后,将其存入到 session 范围中globalsession: 在web项目中,必须用在 porlet 环境属性注入设置构造方法方式的属性注入: Car 类在构造方法中有两个属性,分别为 name 和 price。      set 方法属性注入: Employee 类在有两个 set 方法,分别设置普通类型的 name 和引用类型的 Car (使用 ref 指向引用类型的 id 或  name)。      P名称空间的属性注入: 首先需要引入p名称空间:    //引入p名称空间    xmlns:p="http://www.springframework.org/schema/p"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">如果是普通属性:如果是引用类型:SpEL(Spring Expression Language)属性注入(Spring 3.x以上版本)      集合类型属性注入:                      qirui            baoma            benchi          多模块开发配置在加载配置文件的时候,加载多个配置文件在一个配置文件中引入多个配置文件,通过实现IOC 注解开发示例引入jar包: 除了要引入上述的四个包之外,还需要引入aop包。创建 applicationContext.xml ,使用注解开发引入 context 约束(xsd-configuration.html)    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">        组件扫描: 使用IOC注解开发,需要配置组件扫描,也就是哪些包下的类使用IOC的注解。在类上添加注解使用注解设置属性的值属性如果有set方法,将属性注入的注解添加到set方法属性没有set方法,将注解添加到属性上。@Component("UserDao")//相当于配置了一个 其id为UserDao,对应的类为该类public class UserDAOImpl implements UserDAO {@Overridepublic void save() {// TODO Auto-generated method stubSystem.out.println("save");} }注解详解@Component组件注解,用于修饰一个类,将这个类交给 Spring 管理。有三个衍生的注解,功能类似,也用来修饰类。@Controller:修饰 web 层类@Service:修饰 service 层类@Repository:修饰 dao 层类2.属性注入普通属性使用 @Value 来设置属性的值对象属性使用 @Autowired  ,这个注解是按照类型来进行属性注入的。如果希望按照 bean 的名称或id进行属性注入,需要用 @Autowired 和 @Qualifier 一起使用实际开发中,使用 @Resource(name=" ") 来进行按照对象的名称完成属性注入3.其他注解@PostConstruct 相当于 init-method,用于初始化函数的注解@PreDestroy 相当于 destroy-method,用于销毁函数的注解@Scope 作用范围的注解,常用的是默认单例,还有多例 @Scope("prototype")IOC 的 XML 和注解开发比较适用场景:XML 适用于任何场景;注解只适合自己写的类,不是自己提供的类无法添加注解。可以使用 XML 管理 bean,使用注解来进行属性注入AOP开发AOP 是 Aspect Oriented Programming 的缩写,意为面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,是OOP的延续。AOP 能够对程序进行增强,在不修改源码的情况下,可以进行权限校验,日志记录,性能监控,事务控制等。也就是说功能分为两大类,一类是核心业务功能,一类是辅助增强功能。两类功能彼此独立进行开发。比如登录功能是核心业务功能,日志功能是辅助增强功能,如果有需要,将日志和登录编制在一起。辅助功能就称为切面,这种能选择性的、低耦合的把切面和核心业务功能结合的编程思想称为切面编程。底层实现JDK 动态代理只能对实现了接口的类产生代理。Cglib 动态代理可以对没有实现接口的类产生代理对象,生成的是子类对象。使用 JDK 动态代理:public interface UserDao {public void insert();public void delete();public void update();public void query();}实现类:public class UserDaoImpl implements UserDao { @Override public void insert() { System.out.println("insert"); } @Override public void delete() { System.out.println("delete"); } @Override public void update() { System.out.println("update"); } @Override public void query() { System.out.println("query"); } }JDK 代理:public class JDKProxy implements InvocationHandler{private UserDao userDao;public JDKProxy(UserDao userDao){this.userDao=userDao;}public UserDao createProxy(){UserDao userDaoProxy=(UserDao)Proxy.newProxyInstance(userDao.getClass().getClassLoader(),userDao.getClass().getInterfaces(), this);return userDaoProxy;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if("update".equals(method.getName())){System.out.println("权限校验");return method.invoke(userDao, args);}return method.invoke(userDao, args);}}通过动态代理增强了 update 函数。 测试类:public class Demo1 {@Testpublic void demo1(){UserDao userDao=new UserDaoImpl();UserDao proxy=new JDKProxy(userDao).createProxy();proxy.insert();proxy.delete();proxy.update();proxy.query();}}运行结果为:insertdelete权限校验updatequeryCglibCglib 是第三方开源代码生成类库,可以动态添加类的属性和方法。与上边JDK代理不同,Cglib的使用方式如下:public class CglibProxy implements MethodInterceptor{//传入增强的对象private UserDao customerDao;public CglibProxy(UserDao userDao){this.userDao=userDao;}public UserDao createProxy(){Enhancer enhancer=new Enhancer();enhancer.setSuperclass(userDao.getClass());enhancer.setCallback(this);UserDao proxy=(UserDao)enhancer.create();return proxy;}@Overridepublic Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {if("save".equals(method.getName())){System.out.println("enhance function");return methodProxy.invokeSuper(proxy, args);}return methodProxy.invokeSuper(proxy, args);}}如果实现了接口的类,底层采用JDK代理。如果不是实现了接口的类,底层采用 Cglib代理。IOC与传统方式的比较获取对象方式:传统通过 new 关键字主动创建一个对象。IOC 方式中,将对象的生命周期交给 Spring 管理,直接从 Spring 获取对象。也就是控制反转————将控制权从自己手中交到了 Spring 手中。Spring 的 AOP 开发(AspectJ 的 XML 方式)AspectJ 是一个 AOP 的框架,Spring 引入 AspectJ,基于 AspectJ 进行 AOP 的开发。相关术语Joinpoint: 连接点,可以被拦截到的点。也就是可以被增强的方法都是连接点。Pointcut: 切入点,真正被拦截到的点,也就是真正被增强的方法Advice: 通知,方法层面的增强。对某个方法进行增强的方法,比如对 save 方法进行权限校验,权限校验的方法称为通知。Introduction: 引介,类层面的增强。Target: 目标,被增强的对象(类)。Weaving: 织入,将 advice 应用到 target 的过程。Proxy: 代理对象,被增强的对象。Aspect: 切面,多个通知和多个切入点的组合。使用方法引入相关包引入配置文件    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">编写目标类并配置:public class ProductDaoImpl implements ProductDao {@Overridepublic void save() {System.out.println("save");}@Overridepublic void update() {System.out.println("update");}@Overridepublic void find() {System.out.println("find");}@Overridepublic void delete() {System.out.println("delete");}}编写切面类,假设用于权限验证并配置public class MyAspectXML {public void checkPri(){System.out.println("check auth");}}通过AOP配置完成对目标类的增强 通知类型前置通知:在目标方法执行前操作,可以获得切入点信息public void checkPri(JoinPoint joinPoint){System.out.println("check auth "+joinPoint);}后置通知:在目标方法执行后操作,可以获得方法返回值public void writeLog(Object result){    System.out.println("writeLog "+result);}环绕通知:在目标方法执行前和后操作,可以阻止目标方法执public Object around(ProceedingJoinPoint joinPoint) throws Throwable{System.out.println("before");Object result=joinPoint.proceed();System.out.println("after");return result;}异常抛出通知:程序出现异常时操作public void afterThrowing(Throwable ex){System.out.println("exception "+ex.getMessage());}最终通知:相当于finally块,无论代码是否有异常,都会执行public void finallyFunc(){System.out.println("finally");}引介通知:不常用Spring 切入点表达式基于 execution 函数完成语法:[访问修饰符] 方法返回值 包名.类名.方法名(参数)其中任意字段可以使用*代替表示任意值Spring 的 AOP 基于 AspectJ 注解开发开发步骤引入jar包设置配置文件:xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">编写配置目标类public class OrderDao {public void save(){System.out.println("save order");}public void update(){System.out.println("update order");}public void delete(){System.out.println("delete order");}public void find(){System.out.println("find order");}}开启aop注解自动代理编写切面类并配置@Aspectpublic class MyAspectAnno {@Before(value="execution(* demo1.OrderDao.save(..))")public void before(){System.out.println("before");}}注解通知类型@Before: 前置通知@AfterReturning: 后置通知@AfterReturning(value="execution(* demo1.OrderDao.save(..))",returning="result")public void after(Object result){System.out.println("after "+result);}@Around:环绕通知@Around(value="execution(* demo1.OrderDao.save(..))")public Object around(ProceedingJoinPoint joinPoint) throws Throwable{System.out.println("before");Object obj=joinPoint.proceed();System.out.println("after");return obj;}@AfterThrowing: 抛出异常@AfterThrowing(value="execution(* demo1.OrderDao.save(..))",throwing="e")public void afterThrowing(Throwable e){System.out.println("exception:"+e.getMessage();}@After: 最终通知@After(value="execution(* demo1.OrderDao.save(..))")public void after(){System.out.println("finally");}@PointCut:切入点注解@PointCut(value="execution(* demo1.OrderDao.save(..))")private void pointcut1(){}此时,在上述通知的注解中,value可以替换为该函数名,例如:@After(value="MyAspect.pointcut1()")public void after(){System.out.println("finally");}这个注解的好处是,只需要维护切入点即可,不用在修改时修改每个注解。Spring 的 JDBC 模板Spring 对持久层也提供了解决方案,也就是 ORM 模块和 JDBC 的模板。针对 JDBC ,提供了 org.springframework.jdbc.core.JdbcTemplate 作为模板类。使用 JDBC 模板引入jar包,数据库驱动,Spring 的 jdbc 相关包。基本使用:public void demo1(){    //创建连接池DriverManagerDataSource dataSource=new DriverManagerDataSource();dataSource.setDriverClassName("com.mysql.jdbc.Driver");dataSource.setUrl("jdbc:mysql:///spring4");dataSource.setUsername("root");dataSource.setPassword("123456");//创建JDBC模板 JdbcTemplate jdbcTemplate=new JdbcTemplate(dataSource);jdbcTemplate.update("insert into account values (null,?,?)", "xiaoming",1000d);}将连接池和模板交给 Spring 管理配置文件: 测试文件:@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("classpath:applicationContext.xml")public class JdbcDemo2 {@Resource(name="jdbcTemplate")private JdbcTemplate jdbcTemplate;@Testpublic void demo2(){jdbcTemplate.update("insert into account values (null,?,?)", "xiaolan",1000d);}}使用开源数据库连接池使用 DBCP 的配置:使用 C3P0 的配置:引入外部属性文件首先建立外部属性文件:jdbc.driverClass=com.mysql.jdbc.Driverjdbc.url=jdbc:mysql://192.168.66.128/spring4jdbc.username=rootjdbc.password=123456然后对属性文件进行配置:CRUD操作insert, update, delete 语句都借助模板的 update 方法进行操作。public void demo(){jdbcTemplate.update("insert into account values (null,?,?)", "xiaoda",1000d);jdbcTemplate.update("update account set name=?,money=? where id=?", "xiaoda",1000d,2);jdbcTemplate.update("delete from account where id=?", 6);}查询操作:public void demo3(){String name=jdbcTemplate.queryForObject("select name from account where id=?",String.class,5);long count=jdbcTemplate.queryForObject("select count(*) from account",Long.class);}将返回的结果封装成为类:public void demo4(){Account account=jdbcTemplate.queryForObject("select * from account where id=?", new MyRowMapper(),5);}其中:class MyRowMapper implements RowMapper{@Overridepublic Account mapRow(ResultSet rs, int rowNum) throws SQLException {Account account=new Account();account.setId(rs.getInt("id"));account.setName(rs.getString("name"));account.setMoney(rs.getDouble("money"));return account;}}Spring的事务管理事务事务是指逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部失败。具有四个特性:原子性:事务不可分一致性:事务执行前后数据完整性保持一致隔离性:一个事务的执行不应该受到其他事务干扰持久性:一旦事务结束,数据就持久化到数据库如果不考虑隔离性会引发安全性问题:读问题:脏读:一个事务读到另一个事务未提交的数据不可重复读:一个事务读到另一个事务已经提交的 update 数据,导致一个事务中多次查询结果不一致幻读:一个事务读到另一个事务已经提交的 insert 数据,导致一个事务中多次查询结果不一致写问题:丢失更新解决读问题:设置事务隔离级别Read uncommitted: 未提交读,无法解决任何读问题Read committed: 已提交读,解决脏读问题Repeatable read: 重复读,解决脏读和不可重复读问题Serializable:序列化,解决所有读问题事务管理APIPlatformTransactionManager: 平台事务管理器这是一个接口,拥有多个不同的实现类,如 DataSourceTransactionManager 底层使用了JDBC 管理事务; HibernateTransactionManager 底层使用了 Hibernate 管理事务。TransactionDefinition: 事务定义信息用于定义事务的相关信息,如隔离级别、超时信息、传播行为、是否只读等TransactionStatus: 事务的状态用于记录在事务管理过程中,事务的状态的对象。上述API的关系: Spring 在进行事务管理的时候,首先平台事务管理器根据事务定义信息进行事务管理,在事务管理过程当中,产生各种此状态,将这些状态信息记录到事务状态的对象当中。事务的传播行为事务的传播行为主要解决业务层(Service)方法相互调用的问题,也就是不同的业务中存在不同的事务时,如何操作。Spring 中提供了7种事务的传播行为,分为三类:保证多个操作在同一个事务中PROPAGATION_REQUIRED: B方法调用A方法,如果A中有事务,使用A中的事务并将B中的操作包含到该事务中;否则新建一个事务,将A和B中的操作包含进来。(默认)PROPAGATION_SUPPORTS:如果A中有事务,使用A的事务;否则不使用事务PROPAGATION_MANDATORY:如果A中有事务,使用A的事务;否则抛出异常保证多个操作不在同一个事务中PROPAGATION_REQUIRES_NEW:如果A中有事务,将其挂起,创建新事务,只包含自身操作。否则,新建一个事务,只包含自身操作。PROPAGATION_NOT_SUPPORTED:如果A中有事务,挂起,不使用事务。PROPAGATION_NEVER:如果A中有事务,抛出异常,也即不能用事务运行。嵌套事务PROPAGATION_NESTED:如果A有事务,按照A的事务执行,执行完成后,设置一个保存点,然后执行B的操作。如果出现异常,可以回滚到最初状态或保存点状态。实例以转账为例,业务层的DAO层类如下:public interface AccountDao {public void outMoney(String from,Double money);public void inMoney(String to,Double money);}public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao{@Overridepublic void outMoney(String from, Double money) { this.getJdbcTemplate().update("update account set money = money - ? where name = ?",money,from);}@Overridepublic void inMoney(String to, Double money) {this.getJdbcTemplate().update("update account set money = money + ? where name = ?",money,to);}}public interface AccountService {public void transfer(String from,String to,Double money);}public class AccountServiceImpl implements AccountService {private AccountDao accountDao;public void setAccountDao(AccountDao accountDao) {this.accountDao = accountDao;}@Overridepublic void transfer(String from, String to, Double money) {accountDao.outMoney(from, money);accountDao.inMoney(to, money);}}在xml中进行类的配置:
  • [新手课堂] Spring 学习笔记
    Spring介绍Spring 是一个开源框架,是一个分层的 JavaEE 一站式框架。所谓一站式框架是指 Spring 有 JavaEE 开发的每一层解决方案。WEB层:SpringMVCService层:Spring的Bean管理,声明式事务DAO层:Spring的JDBC模板,ORM模板优点:IOC:方便解耦合AOP:对程序进行扩展轻量级框架方便与其他框架整合Spring使用Spring开发包解压后的目录介绍:docs: Spring 开发规范和APIlibs: Spring jar 包和源代码schema: Spring 配置文件的约束DataAccess 用于数据访问,WEB 用于页面显示,核心容器也就是IOC部分。控制反转(IOC)控制反转(Inversion of Control)是指将对象的创建权反转(交给)Spring。使用IOC就需要导入IOC相关的包,也就是上图中核心容器中的几个包:beans,context,core,expression四个包。实现原理传统方式创建对象:UserDAO userDAO=new UserDAO();进一步面向接口编程,可以多态:UserDAO userDAO=new UserDAOImpl();这种方式的缺点是接口和实现类高耦合,切换底层实现类时,需要修改源代码。程序设计应该满足OCP元祖,在尽量不修改程序源代码的基础上对程序进行扩展。此时,可以使用工厂模式:class BeanFactory{    public static UserDAO getUserDAO(){        return new UserDAOImpl();    }}此种方式虽然在接口和实现类之间没有耦合,但是接口和工厂之间存在耦合。使用工厂+反射+配置文件的方式,实现解耦,这也是 Spring 框架 IOC 的底层实现。//xml配置文件//<bean id="userDAO" class="xxx.UserDAOImpl"></bean>class BeanFactory{    public static Object getBean(String id){        //解析XML        //反射        Class clazz=Class.forName();        return clazz.newInstance();    }}IOC XML 开发在 docs 文件中包含了 xsd-configuration.hmtl 文件。其中定义了 beans schema。<beans xmlns="http://www.springframework.org/schema/beans"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">    //在此配置bean    <bean id="userService" class="x.y.UserServiceImpl">    </bean></beans>调用类:ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml");UserService userService=(UserService)applicationContext.getBean("userService");userService.save();IOC 和 DIDI 指依赖注入,其前提是必须有 IOC 的环境,Spring 管理这个类的时候将类的依赖的属性注入进来。例如,在UserServiceImpl.java中:public class UserServiceImpl implements UserService{private String name;public void setName(String name){this.name=name;}public void save(){System.out.println("save "+name);}}在配置文件中:<beans xmlns="http://www.springframework.org/schema/beans"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">    <bean id="userService" class="spring.demo1.UserServiceImpl">    <!--配置依赖的属性-->     <property name="name" value="tony"/>    </bean></beans>测试代码:@Testpublic void demo2(){//创建Spring工厂ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml");UserService userService=(UserService)applicationContext.getBean("userService");userService.save();}运行结果:save tony可以看到,在配置文件中配置的属性,在 Spring 管理该类的时候将其依赖的属性成功进行了设置。如果不使用依赖注入,则无法使用接口,只能使用实现类来进行设置,因为接口中没有该属性。Spring 的工厂类BeanFactory: 老版本的工厂类,在调用getBean()方法时,才会生成类的实例。ApplicationContext: 在加载配置文件的时候,就会将 Spring 管理的类都实例化。有两个实现类:ClassPathXmlApplicationContext: 加载类路径下的配置文件FileSystemXmlApplicationContext: 加载磁盘下的配置文件bean标签配置id: 唯一约束,不能出现特殊字符name: 理论上可以重复,但是开发中最好不要。可以出现特殊字符生命周期:init-method: bean被初始化的时候执行的方法destroy-method: bean被销毁的时候执行的方法作用范围:scope: bean的作用范围,有如下几种,常用的是前两种singleton: 默认使用单例模式创建prototype: 多例request: 在web项目中,spring 创建类后,将其存入到 request 范围中session: 在web项目中,spring 创建类后,将其存入到 session 范围中globalsession: 在web项目中,必须用在 porlet 环境属性注入设置构造方法方式的属性注入: Car 类在构造方法中有两个属性,分别为 name 和 price。<bean id="car" class="demo.Car">    <constructor-arg name="name" value="bmw">    <constructor-arg name="price" value="123"></bean>set 方法属性注入: Employee 类在有两个 set 方法,分别设置普通类型的 name 和引用类型的 Car (使用 ref 指向引用类型的 id 或  name)。<bean id="employee" class="demo.Employee">    <property name="name" value="xiaoming">    <property name="car" ref="car"></bean>P名称空间的属性注入: 首先需要引入p名称空间:<beans xmlns="http://www.springframework.org/schema/beans"    //引入p名称空间    xmlns:p="http://www.springframework.org/schema/p"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"></beans>如果是普通属性:<bean id="car" class="demo.Car" p:name="bmv" p:price="123"></bean>如果是引用类型:<bean id="employee" class="demo.Employee" p:name="xiaoming" p:car-ref:"car"></bean>SpEL(Spring Expression Language)属性注入(Spring 3.x以上版本)<bean id="car" class="demo.Car">    <property name="name" value="#{'xiaoming'}">    <property name="car" ref="#{car}"></bean>集合类型属性注入:<bean id="car" class="demo.Car">    <property name="namelist">        <list>            <value>qirui</value>            <value>baoma</value>            <value>benchi</value>        </list>    </property></bean>多模块开发配置在加载配置文件的时候,加载多个配置文件在一个配置文件中引入多个配置文件,通过实现IOC 注解开发示例引入jar包: 除了要引入上述的四个包之外,还需要引入aop包。创建 applicationContext.xml ,使用注解开发引入 context 约束(xsd-configuration.html)<beans xmlns="http://www.springframework.org/schema/beans"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">         <!-- bean definitions here --></beans>组件扫描: 使用IOC注解开发,需要配置组件扫描,也就是哪些包下的类使用IOC的注解。<context:component-scan base-package="demo1">在类上添加注解使用注解设置属性的值属性如果有set方法,将属性注入的注解添加到set方法属性没有set方法,将注解添加到属性上。@Component("UserDao")//相当于配置了一个<bean> 其id为UserDao,对应的类为该类public class UserDAOImpl implements UserDAO {@Overridepublic void save() {// TODO Auto-generated method stubSystem.out.println("save");} }注解详解@Component组件注解,用于修饰一个类,将这个类交给 Spring 管理。有三个衍生的注解,功能类似,也用来修饰类。@Controller:修饰 web 层类@Service:修饰 service 层类@Repository:修饰 dao 层类2.属性注入普通属性使用 @Value 来设置属性的值对象属性使用 @Autowired  ,这个注解是按照类型来进行属性注入的。如果希望按照 bean 的名称或id进行属性注入,需要用 @Autowired 和 @Qualifier 一起使用实际开发中,使用 @Resource(name=" ") 来进行按照对象的名称完成属性注入3.其他注解@PostConstruct 相当于 init-method,用于初始化函数的注解@PreDestroy 相当于 destroy-method,用于销毁函数的注解@Scope 作用范围的注解,常用的是默认单例,还有多例 @Scope("prototype")IOC 的 XML 和注解开发比较适用场景:XML 适用于任何场景;注解只适合自己写的类,不是自己提供的类无法添加注解。可以使用 XML 管理 bean,使用注解来进行属性注入AOP开发AOP 是 Aspect Oriented Programming 的缩写,意为面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,是OOP的延续。AOP 能够对程序进行增强,在不修改源码的情况下,可以进行权限校验,日志记录,性能监控,事务控制等。也就是说功能分为两大类,一类是核心业务功能,一类是辅助增强功能。两类功能彼此独立进行开发。比如登录功能是核心业务功能,日志功能是辅助增强功能,如果有需要,将日志和登录编制在一起。辅助功能就称为切面,这种能选择性的、低耦合的把切面和核心业务功能结合的编程思想称为切面编程。底层实现JDK 动态代理只能对实现了接口的类产生代理。Cglib 动态代理可以对没有实现接口的类产生代理对象,生成的是子类对象。使用 JDK 动态代理:public interface UserDao {public void insert();public void delete();public void update();public void query();}实现类:public class UserDaoImpl implements UserDao { @Override public void insert() { System.out.println("insert"); } @Override public void delete() { System.out.println("delete"); } @Override public void update() { System.out.println("update"); } @Override public void query() { System.out.println("query"); } }JDK 代理:public class JDKProxy implements InvocationHandler{private UserDao userDao;public JDKProxy(UserDao userDao){this.userDao=userDao;}public UserDao createProxy(){UserDao userDaoProxy=(UserDao)Proxy.newProxyInstance(userDao.getClass().getClassLoader(),userDao.getClass().getInterfaces(), this);return userDaoProxy;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if("update".equals(method.getName())){System.out.println("权限校验");return method.invoke(userDao, args);}return method.invoke(userDao, args);}}通过动态代理增强了 update 函数。 测试类:public class Demo1 {@Testpublic void demo1(){UserDao userDao=new UserDaoImpl();UserDao proxy=new JDKProxy(userDao).createProxy();proxy.insert();proxy.delete();proxy.update();proxy.query();}}运行结果为:insertdelete权限校验updatequeryCglibCglib 是第三方开源代码生成类库,可以动态添加类的属性和方法。与上边JDK代理不同,Cglib的使用方式如下:public class CglibProxy implements MethodInterceptor{//传入增强的对象private UserDao customerDao;public CglibProxy(UserDao userDao){this.userDao=userDao;}public UserDao createProxy(){Enhancer enhancer=new Enhancer();enhancer.setSuperclass(userDao.getClass());enhancer.setCallback(this);UserDao proxy=(UserDao)enhancer.create();return proxy;}@Overridepublic Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {if("save".equals(method.getName())){System.out.println("enhance function");return methodProxy.invokeSuper(proxy, args);}return methodProxy.invokeSuper(proxy, args);}}如果实现了接口的类,底层采用JDK代理。如果不是实现了接口的类,底层采用 Cglib代理。IOC与传统方式的比较获取对象方式:传统通过 new 关键字主动创建一个对象。IOC 方式中,将对象的生命周期交给 Spring 管理,直接从 Spring 获取对象。也就是控制反转————将控制权从自己手中交到了 Spring 手中。Spring 的 AOP 开发(AspectJ 的 XML 方式)AspectJ 是一个 AOP 的框架,Spring 引入 AspectJ,基于 AspectJ 进行 AOP 的开发。相关术语Joinpoint: 连接点,可以被拦截到的点。也就是可以被增强的方法都是连接点。Pointcut: 切入点,真正被拦截到的点,也就是真正被增强的方法Advice: 通知,方法层面的增强。对某个方法进行增强的方法,比如对 save 方法进行权限校验,权限校验的方法称为通知。Introduction: 引介,类层面的增强。Target: 目标,被增强的对象(类)。Weaving: 织入,将 advice 应用到 target 的过程。Proxy: 代理对象,被增强的对象。Aspect: 切面,多个通知和多个切入点的组合。使用方法引入相关包引入配置文件<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here --></beans>编写目标类并配置:public class ProductDaoImpl implements ProductDao {@Overridepublic void save() {System.out.println("save");}@Overridepublic void update() {System.out.println("update");}@Overridepublic void find() {System.out.println("find");}@Overridepublic void delete() {System.out.println("delete");}}<bean id="productDao" class="demo1.ProductDaoImpl"></bean>编写切面类,假设用于权限验证并配置public class MyAspectXML {public void checkPri(){System.out.println("check auth");}}<bean id="myAspect" class="demo1.MyAspectXML"></bean>通过AOP配置完成对目标类的增强<aop:config><aop:pointcut expression="execution(* demo1.ProductDaoImpl.save(..))" id="pointcut1"/><aop:aspect ref="myAspect"><aop:before method="chechPri" pointcut-ref="pointcut1"/></aop:aspect> </aop:config>通知类型前置通知:在目标方法执行前操作,可以获得切入点信息<aop:before method="chechPri" pointcut-ref="pointcut1"/>public void checkPri(JoinPoint joinPoint){System.out.println("check auth "+joinPoint);}后置通知:在目标方法执行后操作,可以获得方法返回值<aop:after-returning method="writeLog" pointcut-ref="pointcut2" returning="result"/>public void writeLog(Object result){    System.out.println("writeLog "+result);}环绕通知:在目标方法执行前和后操作,可以阻止目标方法执<aop:around method="around" pointcut-ref="pointcut3"/>public Object around(ProceedingJoinPoint joinPoint) throws Throwable{System.out.println("before");Object result=joinPoint.proceed();System.out.println("after");return result;}异常抛出通知:程序出现异常时操作<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="ex"/>public void afterThrowing(Throwable ex){System.out.println("exception "+ex.getMessage());}最终通知:相当于finally块,无论代码是否有异常,都会执行<aop:after method="finallyFunc" pointcut-ref="pointcut4"/>public void finallyFunc(){System.out.println("finally");}引介通知:不常用Spring 切入点表达式基于 execution 函数完成语法:[访问修饰符] 方法返回值 包名.类名.方法名(参数)其中任意字段可以使用*代替表示任意值Spring 的 AOP 基于 AspectJ 注解开发开发步骤引入jar包设置配置文件:<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"></beans>编写配置目标类<bean id="orderDao" class="demo1.OrderDao"></bean>public class OrderDao {public void save(){System.out.println("save order");}public void update(){System.out.println("update order");}public void delete(){System.out.println("delete order");}public void find(){System.out.println("find order");}}开启aop注解自动代理<aop:aspectj-autoproxy/>编写切面类并配置@Aspectpublic class MyAspectAnno {@Before(value="execution(* demo1.OrderDao.save(..))")public void before(){System.out.println("before");}}<bean id="myAspect" class="demo1.MyAspectAnno">注解通知类型@Before: 前置通知@AfterReturning: 后置通知@AfterReturning(value="execution(* demo1.OrderDao.save(..))",returning="result")public void after(Object result){System.out.println("after "+result);}@Around:环绕通知@Around(value="execution(* demo1.OrderDao.save(..))")public Object around(ProceedingJoinPoint joinPoint) throws Throwable{System.out.println("before");Object obj=joinPoint.proceed();System.out.println("after");return obj;}@AfterThrowing: 抛出异常@AfterThrowing(value="execution(* demo1.OrderDao.save(..))",throwing="e")public void afterThrowing(Throwable e){System.out.println("exception:"+e.getMessage();}@After: 最终通知@After(value="execution(* demo1.OrderDao.save(..))")public void after(){System.out.println("finally");}@PointCut:切入点注解@PointCut(value="execution(* demo1.OrderDao.save(..))")private void pointcut1(){}此时,在上述通知的注解中,value可以替换为该函数名,例如:@After(value="MyAspect.pointcut1()")public void after(){System.out.println("finally");}这个注解的好处是,只需要维护切入点即可,不用在修改时修改每个注解。Spring 的 JDBC 模板Spring 对持久层也提供了解决方案,也就是 ORM 模块和 JDBC 的模板。针对 JDBC ,提供了 org.springframework.jdbc.core.JdbcTemplate 作为模板类。使用 JDBC 模板引入jar包,数据库驱动,Spring 的 jdbc 相关包。基本使用:public void demo1(){    //创建连接池DriverManagerDataSource dataSource=new DriverManagerDataSource();dataSource.setDriverClassName("com.mysql.jdbc.Driver");dataSource.setUrl("jdbc:mysql:///spring4");dataSource.setUsername("root");dataSource.setPassword("123456");//创建JDBC模板 JdbcTemplate jdbcTemplate=new JdbcTemplate(dataSource);jdbcTemplate.update("insert into account values (null,?,?)", "xiaoming",1000d);}将连接池和模板交给 Spring 管理配置文件:<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource;"><property name="driverClassName" value="com.mysql.jdbc.Driver"></property><property name="url" value="jdbc:mysql:///spring4"></property><property name="username" value="root"></property><property name="password" value="123456"></property></bean><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate;"> <property name="dataSource" ref="dataSource"></property></bean>测试文件:@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("classpath:applicationContext.xml")public class JdbcDemo2 {@Resource(name="jdbcTemplate")private JdbcTemplate jdbcTemplate;@Testpublic void demo2(){jdbcTemplate.update("insert into account values (null,?,?)", "xiaolan",1000d);}}使用开源数据库连接池使用 DBCP 的配置:<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"><property name="driverClassName" value="com.mysql.jdbc.Driver"></property><property name="url" value="jdbc:mysql://192.168.66.128/spring4"></property><property name="username" value="root"></property><property name="password" value="123456"></property>使用 C3P0 的配置:<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"><property name="driverClass" value="com.mysql.jdbc.Driver"></property><property name="jdbcUrl" value="jdbc:mysql://192.168.66.128/spring4"></property><property name="user" value="root"></property><property name="password" value="123456"></property></bean>引入外部属性文件首先建立外部属性文件:jdbc.driverClass=com.mysql.jdbc.Driverjdbc.url=jdbc:mysql://192.168.66.128/spring4jdbc.username=rootjdbc.password=123456然后对属性文件进行配置:<context:property-placeholder location="classpath:jdbc.properties"/><bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"><property name="driverClass" value="${jdbc.driverClass}"></property><property name="jdbcUrl" value="${jdbc.url}"></property><property name="user" value="${jdbc.username}"></property><property name="password" value="${jdbc.password}"></property></bean>CRUD操作insert, update, delete 语句都借助模板的 update 方法进行操作。public void demo(){jdbcTemplate.update("insert into account values (null,?,?)", "xiaoda",1000d);jdbcTemplate.update("update account set name=?,money=? where id=?", "xiaoda",1000d,2);jdbcTemplate.update("delete from account where id=?", 6);}查询操作:public void demo3(){String name=jdbcTemplate.queryForObject("select name from account where id=?",String.class,5);long count=jdbcTemplate.queryForObject("select count(*) from account",Long.class);}将返回的结果封装成为类:public void demo4(){Account account=jdbcTemplate.queryForObject("select * from account where id=?", new MyRowMapper(),5);}其中:class MyRowMapper implements RowMapper<Account>{@Overridepublic Account mapRow(ResultSet rs, int rowNum) throws SQLException {Account account=new Account();account.setId(rs.getInt("id"));account.setName(rs.getString("name"));account.setMoney(rs.getDouble("money"));return account;}}Spring的事务管理事务事务是指逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部失败。具有四个特性:原子性:事务不可分一致性:事务执行前后数据完整性保持一致隔离性:一个事务的执行不应该受到其他事务干扰持久性:一旦事务结束,数据就持久化到数据库如果不考虑隔离性会引发安全性问题:读问题:脏读:一个事务读到另一个事务未提交的数据不可重复读:一个事务读到另一个事务已经提交的 update 数据,导致一个事务中多次查询结果不一致幻读:一个事务读到另一个事务已经提交的 insert 数据,导致一个事务中多次查询结果不一致写问题:丢失更新解决读问题:设置事务隔离级别Read uncommitted: 未提交读,无法解决任何读问题Read committed: 已提交读,解决脏读问题Repeatable read: 重复读,解决脏读和不可重复读问题Serializable:序列化,解决所有读问题事务管理APIPlatformTransactionManager: 平台事务管理器这是一个接口,拥有多个不同的实现类,如 DataSourceTransactionManager 底层使用了JDBC 管理事务; HibernateTransactionManager 底层使用了 Hibernate 管理事务。TransactionDefinition: 事务定义信息用于定义事务的相关信息,如隔离级别、超时信息、传播行为、是否只读等TransactionStatus: 事务的状态用于记录在事务管理过程中,事务的状态的对象。上述API的关系: Spring 在进行事务管理的时候,首先平台事务管理器根据事务定义信息进行事务管理,在事务管理过程当中,产生各种此状态,将这些状态信息记录到事务状态的对象当中。事务的传播行为事务的传播行为主要解决业务层(Service)方法相互调用的问题,也就是不同的业务中存在不同的事务时,如何操作。Spring 中提供了7种事务的传播行为,分为三类:保证多个操作在同一个事务中PROPAGATION_REQUIRED: B方法调用A方法,如果A中有事务,使用A中的事务并将B中的操作包含到该事务中;否则新建一个事务,将A和B中的操作包含进来。(默认)PROPAGATION_SUPPORTS:如果A中有事务,使用A的事务;否则不使用事务PROPAGATION_MANDATORY:如果A中有事务,使用A的事务;否则抛出异常保证多个操作不在同一个事务中PROPAGATION_REQUIRES_NEW:如果A中有事务,将其挂起,创建新事务,只包含自身操作。否则,新建一个事务,只包含自身操作。PROPAGATION_NOT_SUPPORTED:如果A中有事务,挂起,不使用事务。PROPAGATION_NEVER:如果A中有事务,抛出异常,也即不能用事务运行。嵌套事务PROPAGATION_NESTED:如果A有事务,按照A的事务执行,执行完成后,设置一个保存点,然后执行B的操作。如果出现异常,可以回滚到最初状态或保存点状态。实例以转账为例,业务层的DAO层类如下:public interface AccountDao {public void outMoney(String from,Double money);public void inMoney(String to,Double money);}public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao{@Overridepublic void outMoney(String from, Double money) { this.getJdbcTemplate().update("update account set money = money - ? where name = ?",money,from);}@Overridepublic void inMoney(String to, Double money) {this.getJdbcTemplate().update("update account set money = money + ? where name = ?",money,to);}}public interface AccountService {public void transfer(String from,String to,Double money);}public class AccountServiceImpl implements AccountService {private AccountDao accountDao;public void setAccountDao(AccountDao accountDao) {this.accountDao = accountDao;}@Overridepublic void transfer(String from, String to, Double money) {accountDao.outMoney(from, money);accountDao.inMoney(to, money);}}在xml中进行类的配置:<bean id="accountService" class="tx.demo.AccountServiceImpl"><property name="accountDao" ref="accountDao"/></bean><bean id="accountDao" class="tx.demo.AccountDaoImpl"><property name="dataSource" ref="dataSource"/></bean>