Java基础(十)
1.JDBC概述
- JDBC为访问不同的数据库提供了统一 的接口,为使用者屏蔽了细节问题。
- Java程序员使用JDBC,可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的各种操作。
- JDBC是Java提供一套用于数据库操作的接口API,Java程序员只需要面向着套接口编程即可,不同的数据库厂商,需要针对这套接口,提供不同实现。
- JDBC API是一系列的接口,它统一和规范了应用程序与数据库的连接,执行SQL语句,并得到返回结果等各类操作,相关类和接口在java.sql与javax.sql包中。
2. JDBC 程序编写步骤
- 注册驱动–加载Driver类
- 获取连接–得到Connection
- 执行增删改查–发送SQL 给mysql执行
- 释放资源–关闭相关连接
例子:
DROP TABLE IF EXISTS `actor`;
CREATE TABLE `actor` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL DEFAULT '',
`sex` char(1) NOT NULL DEFAULT '女',
`borndate` datetime DEFAULT NULL,
`phone` varchar(12) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
public class Jdbc01 {
public static void main(String[] args) throws SQLException {
//1、注册驱动
Driver driver = new Driver();
//2、得到连接
//(1)jdbc:mysql://规定好表示协议,通过jdbc的方式连接mysql
//(2) localhost 主机 ,可以是ip地址
//(3) 3306:表示mysql监听的端口
//(4)demo 连接到mysql bdms的哪个数据库
//(5) mysql的连接本质就是socket连接
String url = "jdbc:mysql://localhost:3306/demo";
//将用户名和密码放入到Properties 对象
Properties properties = new Properties();
//user 和 password 是规定好的键
properties.setProperty("user","root");
properties.setProperty("password","1123");
Connection connect = driver.connect(url, properties);
//3、执行sql
// String sql = "insert into actor values (null , '刘德华', '男' , '1970-11-11','110' )";
// String sql = "update actor set name = '周星驰'where id = 1";
String sql = "delete from actor where id = 1";
//statement 用于执行静态SQL语句并返回其生成结果的对象。
Statement statement = connect.createStatement();
int row = statement.executeUpdate(sql);//如果是dml语句,返回的就是影响的行数
System.out.println(row > 0 ? "成功": "失败");
//4、关闭连接资源
statement.close();
connect.close();
}
}
3. java 连接sql的5种方式
//方式一:
public void connect01() throws SQLException {
Driver driver = new Driver();//创建Driver对象
String url = "jdbc:mysql://localhost:3306/demo";
//将用户名和密码放入到Properties对象
Properties properties = new Properties();
properties.setProperty("user","root");//用户名
properties.setProperty("password","1123");//密码
Connection connection = driver.connect(url,properties);
System.out.println(connection);
}
@Test
//方式二:
public void connect02() throws ClassNotFoundException, IllegalAccessException, InstantiationException, SQLException {
//使用反射加载Driver类 ,动态加载,加载更加灵活,减少依赖性
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver)aClass.newInstance();
String url = "jdbc:mysql://localhost:3306/demo";
Properties properties = new Properties();
properties.setProperty("user","root");//用户名 properties.setProperty("password","1123");//密码
Connection connection = driver.connect(url,properties);
System.out.println("方式二 "+ connection);
}
//方式三:使用DriverManager替代 driver 进行统一管理
@Test
public void connect03() throws ClassNotFoundException, IllegalAccessException, InstantiationException, SQLException {
//使用反射加载Driver
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver)aClass.newInstance();
String url = "jdbc:mysql://localhost:3306/demo";
String user = "root";
String password = "1123";
DriverManager.registerDriver(driver);//注册Driver驱动
Connection connection = DriverManager.getConnection(url,user,password);
System.out.println("第三种方式= "+connection);
}
//方式四:使用Class.forName自动完成注册,简化代码
//这种方式获取连接是使用最多的,推荐使用
@Test
public void connect04() throws ClassNotFoundException, SQLException {
//使用反射加载了Driver类
//在加载Driver类时,完成注册
/*
源码 :1、静态代码块 ,在类加载时,会执行一次
2、DriverManager.registerDriver(new Driver);
3、因此注册driver的工作已经完成
static{
try{
DriverManager.registerDriver(new Driver());
}catch(SQLException var1){
throw new RuntimeException("Can not register driver");
}
}
*/
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/demo";
String user = "root";
String password = "1123";
Connection connection = DriverManager.getConnection(url,user,password);
System.out.println("第四种方式= "+connection);
}
@Test
//方式五,在方式四的基础上改进,添加配置文件,让连接mysql更灵活
public void connect05() throws IOException, ClassNotFoundException, SQLException {
//通过Properties 对象获取配置文件的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
//获取相关的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
Class.forName("com.mysql.jdbc.Driver");//建议写上
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("方式五 "+ connection);
}
#mysql.properties文件
user=root
password=1123
url=jdbc:mysql://localhost:3306/demo
driver=com.mysql.jdbc.Driver
4. ResultSet[结果集]
- 表示数据库结果集的数据表,通常通过执行查询数据库的语句生成。
- ResultSet对象保持一个光标指向其当前的数据行。最初,光标位于第一行之前
- next方法将光标移动到下一行,并且由于在ResultSet对象中没有更多行时返回false,因此可以在while循环中使用循环来遍历结果集。
public class ResultSet_ {
public static void main(String[] args) throws IOException, SQLException, ClassNotFoundException {
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
Class.forName("com.mysql.jdbc.Driver");//建议写上
Connection connection = DriverManager.getConnection(url, user, password);
Statement statement = connection.createStatement();
String sql = "select id , name ,sex ,borndate from actor ";
ResultSet resultSet = statement.executeQuery(sql);
//使用while取出数据
while(resultSet.next()){//让光标向后移动,如果没有更多行,则返回false
int id = resultSet.getInt(1);//获取该行的第一列
String name= resultSet.getString(2);
String sex = resultSet.getString(3);
Date date = resultSet.getDate(4);
System.out.println(id+"\t"+name+"\t"+sex+"\t"+date);
}
//关闭连接
resultSet.close();
statement.close();
connection.close();
}
}
5. Statement
- Statement对象用于执行静态SQL语句并返回其生成的结果的对象。
- 在连接建立后,需要对数据库进行访问,执行命名或是SQL语句,可以通过Statement[存在SQL注入]、PreparedStatement[预处理]、CallableStatement[存储过程]
- Statement对象执行SQL语句,存在SQL注入风险。
- SQL注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL语句段或命令,恶意攻击数据库。
- 要防范SQL注入,只有用PreparedStatement(从Statement扩展而来)取代Statement就可以了。
6. PreparedStatement
- PreparedStatement执行的SQL语句中的参数用(?)来表示,调用PreparedStatement对象的setXxx()方法来设置这些参数。setXxx()方法有两个参数,一个参数是设置的SQL语句中的参数的索引(从1开始),第二个是设置的SQL语句中的参数的值。
- 调用executeQuery(),返回ResultSet对象
- 调用executeUpdate():执行更新,包括增、删、修改。
7. JDBC API
DriverManager驱动管理 :
getConnection(url,user,pwd);
Collection接口:
createStatement创建Statement对象preparedStatement(sql)生成预处理对象
Statement接口:
executeUpdate(sql)执行dml语句,返回影响的行数。
executeQuery(sql)执行查询,返回ResultSet对象
execute(sql)执行任意的sql,返回布尔值
PrepareStatement接口:
executeUpdate()执行dml
executeQuery()执行查询,返回ResultSet
execute()执行任意sql,返回布尔
setXxx(占位符索引,占位符的值),解决SQL注入
setObject(占位符索引,占位符的值)
ResultSet(结果集):
next()向下移动一行,同时如果没有下一行,返回false
previous()向上移动一行,如果没有上一行,返回false
getXxx(列的索引|列名) 返回对应的值,接收类型是Xxx
getObject(列的索引|列名)返回对应的值,接收类型为Object
8. JDBCUtils
public class JDBCUtils {
//定义相关的属性(4个),因为只需要一份,
private static String user;
private static String password;
private static String url;
private static String driver;
static {
Properties properties = new Properties();
try {
properties.load(new FileInputStream("src\\mysql.properties"));
user = properties.getProperty("user");
password = properties.getProperty("password");
url = properties.getProperty("url");
driver = properties.getProperty("driver");
} catch (IOException e) {
//在实际开发中
//1、将编译异常转换成运行异常
//2、调用者可以选择捕获异常,也可以选择默认处理该异常
throw new RuntimeException(e);
}
}
//连接数据库
public static Connection getConnection(){
try {
return DriverManager.getConnection(url,user,password);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
/**
* 关闭资源:
* 1、ResultSet 结果集
* 2、Statement 或者PreparedStatement
* 3、Connection
* 4、如果需要关闭资源,就传入对象,否则传入Null
*/
public static void close(ResultSet set , Statement statement,Connection connection){
try {
if(set!=null){
set.close();
}
if (statement!=null){
statement.close();
}
if (connection!=null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException();
}
}
}
@Test
public void testDML(){
Connection connection = null;
String sql = "update actor set name = ? where id = ?";
PreparedStatement preparedStatement=null;
try {
connection = JDBCUtils.getConnection();
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,"周星驰");
preparedStatement.setInt(2,3);
preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally { JDBCUtils.close(null,preparedStatement,connection);
}
}
9. 事务
基本介绍:
- JDBC程序中当一个Connection对象创建时,默认情况下自动提交事务:每次执行一个SQL语句时,如果执行成功,就会向数据库自动提交,不能回滚。
- JDBC程序中为了让多个SQL语句作为一个整体执行,需要使用事务。
- 调用Connection的setAutoCommit(false)可以取消自动提交事务
- 在所有SQL语句都执行后,调用commit()方法提交事务。
- 在其中某个操作失败或出现异常时,调用rollback():方法回滚事务。
10. 批处理
-
当需要成批插入或者更新记录时,可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率。
-
JDBC的批处理语句包括下面方法:
addBatch():添加需要批处理的SQL语句或参数
executeBatch()执行批处理语句。
clearBatch():清空批处理包的语句
-
JDBC连接MySQL时,如果要使用批处理功能,在url中加参数 ?rewriteBatchedStatements=true
-
批处理往往和PreparedStatement一起搭配使用,可以既减少编译次数,效率大大提高。
-
例子:
//传统方法,添加5000条数据到admin2 @Test public void noBatch() throws Exception { Connection connection = JDBCUtils.getConnection(); String sql = "insert into admin2 values (null,?,?)"; PreparedStatement preparedStatement = connection.prepareStatement(sql); System.out.println("开始执行......"); long start = System.currentTimeMillis(); for (int i = 0; i <5000 ; i++) { preparedStatement.setString(1,"jack"+i); preparedStatement.setString(2,"666"); preparedStatement.executeUpdate(); } long end = System.currentTimeMillis(); System.out.println("传统的方式耗时="+(end - start));//3726 //关闭连接 JDBCUtils.close(null,preparedStatement,connection); } 批量处理: @Test public void batch() throws Exception { Connection connection = JDBCUtils.getConnection(); String sql = "insert into admin2 values (null,?,?)"; PreparedStatement preparedStatement = connection.prepareStatement(sql); System.out.println("开始执行..."); long start = System.currentTimeMillis(); for (int i = 0; i <5000 ; i++) { preparedStatement.setString(1,"jack"+i); preparedStatement.setString(2,"666"); preparedStatement.addBatch(); if((i + 1)%1000 == 0){ preparedStatement.executeBatch(); preparedStatement.clearBatch(); } } long end = System.currentTimeMillis(); System.out.println("批量的方式耗时="+(end - start));//85 //关闭连接 JDBCUtils.close(null,preparedStatement,connection); }
11. 数据库连接池
基本介绍:
- 预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需要从"缓冲池"中取出一个,使用完毕之后再放回去。
- 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
- 当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
传统获取Connection问题分析:
- 传统的JDBC数据库连接使用DriverManager来获取,每次向数据库建立连接的时候都要将Cnnection加载到内存中,再验证IP地址,用户名和密码(0.05s~1s时间)。需要数据库连接的时候,就向数据库要求一个,频繁的进行数据库连接操作将占用很多的系统资源,容易造成服务器崩溃。
- 每一次数据库连接,使用完后都得断开,如果程序出现异常而未能关闭,将导致数据库内存泄漏,最终将导致重启数据库。
- 传统获取连接的方式,不能控制创建的连接数量,如连接过多,也可能导致内存泄漏,MYSQL崩溃。
- 解决传统开发中的数库连接问题,可以采用数据库连接池技术。
数据库连接池种类:
- JDBC的数据库连接池使用javax.sql.DataSource来表示,DataSource只是一个接口,该接口通常由第三方提供。
- C3P0数据库连接池,速度相对较慢,稳定性不错(hibernate、Spring)
- DBCP数据库连接池,速度相对c3p0较块,但不稳定。
- Proxool数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
- BoneCP数据库连接池,速度快。
- Druid(德鲁伊)是阿里提供的数据库连接池,集DBCP、C3P0、Proxool优于自一身的数据库连接池。
12. Apache-DBUtils
基本介绍:
- commons-dbutils是Apache组织提供的一个开源JDBC工具类库,它是对JDBC的封装,使用dbutils能极大简化jdbc编码的工作量。
- DbUtils类:
- QueryRunner类:该类封装了SQL的执行,是线程安全的。可以实现增删改查、批处理
- 使用QueryRunner类实现查询
- ResultSetHandler接口:该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式。
```java
ArrayHandler:把结果集的第一行数据转换成对象数组。
ArrayListHander:把结果集中的每一行数据都转换成一个数组,再存放到List中
BeanListHandler:将结果集中的每一行元素封装到一个对应的JavaBean实例中,存放到List里
ColumnListHandler:将结果集中某一列的数据存放到List中。
KeyedHandler(name):将结果集中的每行数据都封装到Map里,再把这些map再存放到一个map里,其key为指定的key
MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后存放到List
```
13. 正则表达式
就是用某种模式去匹配字符串的一个公式。
```java
String content="字符串";
String regStr= "正则表达式";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
//java匹配默认为贪婪匹配,尽可能匹配多的
//java匹配中的非贪婪匹配:在限定符后面加?例如"\\d+?"
```
- 元字符-转义号 \\
\\\符号 说明:使用正则表达式去检索某些特殊字符的时候,需要用到转义符号。
在Java的正则表达式中,两个 \\\代表其他语句中的一个\
需要用到转义符号的字符有以下: * + ( ) $ / \ ? [ ] ^ { }
- 元字符-字符匹配符
| 符号 | 说明 | 示例 | 解释 |
| ----- | -------------------------------------------------- | -------------- | ---------------------------------------------------- |
| [ ] | 可接收的字符列表 | [efgh] | e、f、g、h中的任意1字符 |
| [^ ] | 不可接受的字符列表 | [^abc] | 除a、b、c之外的任意1字符,包括数字和特殊符号 |
| - | 连字符 | A-Z | 任意单个大写字母 |
| . | 匹配除\n以外的任何字符 | a..b | 以a开头,b结尾,中间包括2个任意字符的长度为4的字符串 |
| \\\d | 匹配单个数字字符 | \\\d{3}{\\\d}? | 包含3个或4个数字的字符串 |
| \\\D | 匹配单个非数字字符,相当于[^0-9] | \\\D{\\\d}* | 以单个非数字字符开头,后接任意个数字字符串 |
| \\\w | 匹配单个数字、大小写字母字符,相当于[0-9a-zA-Z] | \\\d{3}\\\w{4} | 以3个数字字符开头的长度为7的数字字母字符串 |
| \\\W | 匹配单个非字符、大小写字母字符、相当于[^0-9a-zA-Z] | \\\W+\\\d{2} | 以至少1个非数字字母字符开头,2个数字字符结尾的字符串 |
| \\\s | 匹配任何空白字符(空格,制表符等) | | |
| \\\S | 匹配任何非空白字符,和\\\s刚好相反 | | |
- java正则表达式默认是区分字母大小写的 ,不区分大小写的方式
(?i)abc 表示abc都不区分大小写
a(?i)bc表示bc不区分大小写
a((?i)b)c 表示只有b不区分大小写
Patttern pat = Pattern.complile(regEx,Pattern.CASE_INSENSITIVE);
- 元字符-选择匹配符
| 匹配"|"之前或之后的表达式
- 元字符-限定符
| 符号 | 含义 | 示例 | 说明 | 匹配输入 |
| ----- | ------------------------------ | ----------- | -------------------------------------------------- | ----------------------- |
| * | 指定字符重复0次或n次 | (abc)* | 仅包含任意个abc的字符串,等效于\w* | abc、abcabcabc |
| + | 指定字符重复1次或n次(至少一次) | m+(abc)* | 以至少1个m开头,后接任意个abc的字符串 | m、mabc、mabcabc |
| ? | 指定字符重复0次或1次(最多一次) | m+abc? | 以至少1个m开头,后接ab或abc的字符串 | mab、mabc、mmmab |
| (n) | 只能输入n个字符 | [abcd]{3} | 由abcd中字母组成的任意长度为3的字符串 | abc、dbc、adc |
| {n,} | 指定至少n个匹配 | [abcd]{3,} | 由abcd中字母组成的任意长度不小于3的字符串 | aab、dbc、aaabdc |
| {n,m} | 指定至少n个但不多于m个匹配 | [abcd]{3,5} | 由abcd中字母组成的任意长度不小于3,不大于5的字符串 | abc、abcd、aaaaa、bcdab |
- 元字符-定位符
| 符号 | 含义 | 示例 | 说明 | 匹配输入 |
| ---- | ---------------------- | ------------------ | ------------------------------------------------------------ | ---------------- |
| ^ | 指定起始字符 | \^[0-9]+[a-z]* | 以至少1个数字开头,后接任意个小写字母的字符串 | 123、6aa、555efd |
| $ | 指定结束字符 | \^[0-9]\-[a-z]+$ | 以1个数字开头后接连字符"-",并以至少1个小写字母结尾的字符串 | 1-a |
| \\\b | 匹配目标字符串的边界 | han\\\b | 这里说的字符串边界(后面)指的是子串间有空格,或者是目标字符串的结束位置 | sphan、nnhan |
| \\\B | 匹配目标字符串的非边界 | han\\\B | 和\b的含义刚刚相反 | hanshunping |
- 分组:
| 常用分组构造形式 | 说明 |
| ---------------- | ------------------------------------------------------------ |
| (pattern) | 非命名捕获。捕获匹配的字符串,编号为零的第一个捕获的由整个正则表达式模式匹配的文本,其它捕获结果则根据左括号的顺序从1自动编号。 |
| (?<name>pattern) | 命名捕获。将匹配的字符串捕获到一个组名或编号名称中。用于name的字符串不能包含任何标点符号,并且不能以数字开头,可以使用单引号替代尖括号,例如(?'name') |
- 特别分组
| 常用分组构造形式 | 说明 | 例如 |
| ---------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| (?:pattern) | 匹配pattern但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配,这对于用"or"字符(\|)组合模式部件的情况很有用。 | 'industr(?:y\|ies)'是比'industry\|industries'更经济的表达式 |
| (?=pattern) | 它是一个非捕获匹配 | 'Windows(?=95\|98\|NT\|2000)'匹配"Windows 2000"中的"Windows",但不匹配"Windows 3.1"中的"Windows" |
| (?!pattern) | 该表达式匹配不处于匹配pattern的字符的起始点的搜索字符串。他是一个非捕获匹配 | 'Windows(?!95\|98\|NT\|2000)'匹配"Windows 3.1中的"Windows",但不匹配"Windows 2000"中的"Windows" |
- Pattern类
pattern对象是一个正则表达式对象。Pattern类没有公共构造方法,要创建一个Pattern对象,调用其公共静态方法,它返回一个Pattern对象,该方法接受一个正则表达式作为它的第一个参数.比如:Pattern r = Pattern.compile(pattern);
- Matcher类
Matcher对象是输入字符串进行解释和匹配的引擎。与Pattern类一样,Matcher也没有公共的构造方法,需要调用Pattern对象的matcher方法来获得一个Matcher对象。
- PatternSyntaxException
PatternSyntaxException是一个非强制异常类,它表示一个正则表达式模式中的语法错误。
- 分组:可以用圆括号组成一个比较复杂的匹配模式,那么一个圆括号的部分可以看作一个子表达式/一个分组。
- 捕获:把正则表达式中/分组匹配的内容,保存到内存中以数字编号或显示命名的组里,方便后面引用,从左到右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推,组0代表的是整个正则式。
- 反向引用:圆括号的内容被捕获后,可以在这个括号后被使用,从而写出一个比较实用的匹配模式。这种引用既可以在正则表达式内部,也可以是在正则表达式外部,内部反向引用\\\分组号,外部反向引用$分组号。
- 反向引用例子:要匹配两个连续的相同数字:(\\\d)\\\1
要匹配五个连续的相同数字:(\\\d)\\\1{4}
要匹配个位与千位相同,十位与百位相同的数 5225 、1551 (\\\d)(\\\d)\\\2\\\1