1. JDBC简介
1. JDBC基本概念
Java DataBase Connectivity简称JDBC是Java数据库连接, Java语言操作数据库。JDBC本质:其实是官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口。各个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。
2. JDBC快速入门
- 步骤:
1. 导入驱动jar包
2. 注册驱动
3. 获取数据库连接对象 Connection
4. 定义sql
5. 获取执行sql语句的对象 Statement
6. 执行sql,接受返回结果
7. 处理结果
8. 释放资源
导入驱动jar包
首先:在项目下创建libs目录,然后把jar报辅助到该目录下
然后:右键–>Add As Library;将jar包解压:效果如下
代码实现
public class Demo01 {
public static void main(String[] args) throws Exception {
// 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 获取数据库连接对象
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
// 定义sql语句
String sql = "SELECT * FROM USER";
// 获取执行sql语句的对象 Statement
Statement stmt = conn.createStatement();
//6.执行sql
ResultSet set = stmt.executeQuery(sql);
//7.遍历结果集合
int i=1;
while (set.next()) {
String id = set.getString("id");
String username = set.getString("username");
String birthday = set.getString("birthday");
String sex = set.getString("sex");
String address = set.getString("address");
System.out.println(id+":"+username+":"+birthday+":"+sex+":"+address);
}
//8.释放资源
stmt.close();
conn.close();
}
}
Class.forName
是什么?这是反射的东西,可以简单理解为将该字符串路径下的类加载到内存中
遍历Set集合中的getString是通过数据库中的数据名称来一一对应实现的
2. JDBC各个功能
逻辑框架图
1. DriverManager(驱动管理对象)
功能
1.注册驱动,通过静态代码块实现(因为驱动只注册一次)
使用反射机制,直接将类加载到内存中去
Class.forName("com.mysql.jdbc.Driver");
注意:mysql5之后的驱动jar包可以省略注册驱动的步骤。
2.获取数据库连接
public static Connection getConnection(String url, String user, String password)
方法
参数
- url:指定连接的路径(格式:jdbc:mysql://ip地址(域名):端口号/数据库名称 )
- user:用户名
- password:密码
2. Connection(数据库连接对象)
功能
1.获取执行sql的对象
Statement createStatement()
该方法返回一个Statement对象。(没有防止SQL注入功能)PreparedStatement prepareStatement(String sql)
该方法返回预编译的Statement对象,即将SQL语句提交到数据库进行预编译。(有防止SQL注入功能)
2.管理事务
- 开启事务:
void setAutoCommit(boolean autoCommit) :
调用该方法设置参数为false,即开启事务 - 提交事务:
void commit()
- 回滚事务:
void rollback()
3.Statement(执行sql的对象)
执行sql
boolean execute(String sql)
:该方法可以执行任何SQL语句,如果执行后第一个结果是ResultSet对象,则返回true;如果执行后第一个结果为受影响的行数或没有任何结果,则返回false;一般不用这个方法。int executeUpdate(String sql)
:执行DML(insert、update、delete)语句、DDL(create,alter、drop)语句
返回值:影响的行数,可以通过这个影响的行数判断DML语句是否执行成功 返回值>0的则执行成功,反之,则失败。ResultSet executeQuery(String sql)
:执行DQL(select)语句
boolean execute(String sql);需要特别注意,当我使用该方法查询不存在的数据是,它依旧返回true,这是因为,查询返回的结果集合是一个ResultSet对象,但是ResultSet是从表头开始的,而表头一定存在,所以结果一定为true,不管是否真的存在该数据,所以一般情况下不要用这个方法
4.PreparedStatement(执行sql的对象)
- SQL注入问题:在拼接sql时,有一些sql的特殊关键字参与字符串的拼接,是逻辑本身出现错误,导致整个SQL语句逻辑改变
- 输入用户随便,输入密码:sd’ or ‘sd’ = 'sd
- sql:select * from user where username = ‘fhdsjkf’ and password = ‘a’ or ‘a’ = ‘a’ 后面的
or ‘a’ = ‘a’
始终都为true
因此密码逻辑始终正确,导致不需要正确用户名和密码都可以实现登录
举例:用户输入用户名和密码 ,如果数据库中有,则显示登录成功,如果没有,则显示登录失败
public class Demo01 {
public static void main(String[] args) throws Exception {
// 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 获取数据库连接对象
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
// 定义sql语句
System.out.println("请输入用户名");
String user = new Scanner(System.in).next();
System.out.println("请输入密码");
String password = new Scanner(System.in).next();
String sql = "select * from s1 where user = '"+user+"' and passord = '"+password+"' ";
// 获取执行sql语句的对象 Statement
Statement stmt = conn.createStatement();
//6.执行sql
boolean execute = stmt.execute(sql);
if(execute==true){
System.out.println("密码输入正确");
}else {
System.out.println("密码输入错误");
}
//8.释放资源
stmt.close();
conn.close();
}
}
数据库中用户
当我们输入正确的用户名与密码时
当我们使用sql注入时
很明显,没有用户名与密码都没有也可以登录,只有保证or
后面的逻辑是正确即可登录
解决方法
使用PreparedStatement对象;
代码示例
5. ResultSet(结果集)
boolean next():
游标向下移动一行,判断当前行是否是最后一行末尾(是否有数据),如果是,则返回false,如果不是则返回truegetXxx(参数):
获取数据-
Xxx:代表数据类型 如: int getInt() , String getString()
-
参数:
1. int:代表列的编号,从1开始 如: getString(1)
2. String:代表列名称。 如: getDouble(“balance”)
-
数据库中的数据
代码实现
String sql = "SELECT * FROM s1 WHERE id = '2'";
// 获取执行sql语句的对象 Statement
Statement stmt = conn.createStatement();
//6.执行sql
ResultSet set = stmt.executeQuery(sql);
while (set.next()){
// 第一列,int类型数据
int id = set.getInt(1);
// 第二列,String类型数据
String name = set.getString(2);
// 第三列,String类型数据
String passwod = set.getString(3);
System.out.println(id+" "+name+" "+passwod);
}
输出结果
3. JDBC控制事务
事务:一个包含多个步骤的业务操作。如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败。例如 A给B转账,A账户减少,B账户一定要增加,这两个步骤一定要同步,不能一个成功,一个失败。
实现步骤
使用Connection对象来管理事务
-
开启事务:使用
setAutoCommit(boolean autoCommit)
方法 :调用该方法设置参数为false,即开启事务(在执行sql之前开启事务) -
提交事务:commit() (当所有sql都执行完提交事务)
-
回滚事务:rollback() (在catch中回滚事务,既事务回到最开始的地方(开启事务时))
代码示例
public class Demo03 {
public static void main(String[] args) throws Exception {
Connection con =null;
PreparedStatement p1=null;
PreparedStatement p2=null;
try {
Class.forName("com.mysql.jdbc.Driver");
con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
//开启事务
con.setAutoCommit(false);
// 定义sql转账语句
String sql1 = "update money set moneys = moneys - ? where ID = ?";
String sql2 = "update money set moneys = moneys + ? where ID = ?";
//预处理sql语句获得PreparedStatement对象
p1 = con.prepareStatement(sql1);
p2 = con.prepareStatement(sql2);
// 设置参数
//p1
p1.setDouble(1,20);
p1.setInt(2,1);
//p2
p2.setDouble(1,20);
p2.setInt(2,2);
// 执行sql语句
p1.executeUpdate();
p2.executeUpdate();
//提交事务
con.commit();
}
catch (Exception e){
//事务回滚
con.rollback();
}finally {
p2.close();
p1.close();
con.close();
}
}
}
执行前数据库
执行后数据库
如果我们在第一个转账数据后面添加一个异常
// 执行sql语句
p1.executeUpdate();
手动制造异常
int i = 3/0;
p2.executeUpdate();
//提交事务
con.commit();
执行一下代码,并不会出现一个减少,一个不变的情况
4. JDBC连接池
基本概念:JDBC连接池其实就是一个容器(集合),存放数据库连接的容器。当系统初始化好后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容器中获取连接对象,用户访问完之后,会将连接对象归还给容器。
1. C3P0实现
C3P0:数据库连接池技术
步骤:
1. 导入jar包 (两个) c3p0-0.9.5.2.jar mchange-commons-java-0.2.12.jar 。( 不要忘记导入数据库驱动jar包)
2. 定义配置文件:
* 名称: c3p0.properties 或者 c3p0-config.xml
* 路径:直接将文件放在src目录下即可。
3. 创建核心对象:数据库连接池对象 ComboPooledDataSource
4. 获取连接: getConnection
5. 归还连接对象。 Connection.close();
1. 导入jar包 略
2. 定义配置文件
将c3p0.properties 或者 c3p0-config.xml文件复制到src
目录下,然后配置修改参数。
c3p0-config.xml文件
<c3p0-config>
<!-- 使用默认的配置读取连接池对象 -->
<default-config>
<!-- 连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/db4</property>
<property name="user">root</property>
<property name="password">root</property>
<!-- 连接池参数 -->
<!--初始化申请的连接数量-->
<property name="initialPoolSize">5</property>
<!--最大的连接数量-->
<property name="maxPoolSize">10</property>
<!--超时时间-->
<property name="checkoutTimeout">3000</property>
</default-config>
<named-config name="otherc3p0">
<!-- 连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/db3</property>
<property name="user">root</property>
<property name="password">root</property>
<!-- 连接池参数 -->
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">8</property>
<property name="checkoutTimeout">1000</property>
</named-config>
</c3p0-config>
配置上面的参数,与自己连接的数据库参数一致
- driverClass
- jdbcUrl
- user
- password
注意:上面的c3p0-config.xml文件,发现有2个配置信息,这是因为c3p0可以一个文件,配置多个连接配置方案。
当我们创建连接池对象时,什么都不传递时,使用的是默认的配置信息,当传递一个named-config name="otherc3p0"信息,就使用于其对应的配置信息
3. 创建核心对象
数据库连接池对象 ComboPooledDataSource
使用默认配置信息
/ 创建数据库连接池对象DataSource
DataSource pool = new ComboPooledDataSource();
使用其他配置信息
DataSource pool = new ComboPooledDataSource("otherc3p0");
4. 获取连接对象
Connection conn = pool.getConnection();
5. 归还连接对象
conn,close();
全部代码
public class Demo04C3P0 {
public static void main(String[] args) throws Exception {
//创建数据库连接池对象DataSource
DataSource pool = new ComboPooledDataSource("otherc3p0");
Connection conn = pool.getConnection();
String sql = "SELECT * FROM s1 WHERE id = '2'";
// 获取执行sql语句的对象 Statement
Statement stmt = conn.createStatement();
//执行sql
ResultSet set = stmt.executeQuery(sql);
while (set.next()){
// 第一列,int类型数据
int id = set.getInt(1);
// 第二列,String类型数据
String name = set.getString(2);
// 第三列,String类型数据
String passwod = set.getString(3);
System.out.println(id+" "+name+" "+passwod);
}
stmt.close();
conn.close();
}
}
2. Druid实现
Druid实现数据库连接池实现技术,由阿里巴巴提供的
步骤
- 导入jar包 druid-1.0.9.jar
- 定义配置文件:
* 是properties形式的
* 可以叫任意名称,可以放在任意目录- 加载配置文件。Properties
- 获取数据库连接池对象:通过工厂来来获得DruidDataSourceFactory
- 获取连接对象:getConnection
- 归还连接对象: close方法
1. 导入jar包 druid-1.0.9.jar
略
2. 定义配置文件
properties文件
driverClassName=com.mysql.jdbc.Driver (注册驱动路径)
url=jdbc:mysql://localhost:3306/test (数据库连接路径)
username=root (登录名)
password=root (密码)
initialSize=5 (初始连接个数)
maxActive=10 (最大连接个数)
maxWait=3000 (等待3000毫秒)
注意括号里面的不能有,空格也不行
3. 加载配置文件 Properties
/加载配置文件
Properties prop = new Properties();
/**
* 注意,这里使用的Java反射机制
*/
InputStream is = Demo05.class.getClassLoader().getResourceAsStream("druid.properties");
prop.load(is);
4.获取连接池对象
//4.获取连接池对象
DataSource ds = DruidDataSourceFactory.createDataSource(prop);
5.获取连接对象
Connection con = ds.getConnection();
6.归还连接对象:
con.close();
5. Spring JDBC
Spring框架对JDBC的简单封装。提供了一个JDBCTemplate对象简化JDBC的开发
步骤
- 导入jar包
- 创建JdbcTemplate对象。依赖于数据源DataSource
* JdbcTemplate template = new JdbcTemplate(ds);- 调用JdbcTemplate的方法来完成CRUD的操作
不同方法
update()
:执行DML语句。增、删、改语句queryForMap()
:查询结果将结果集封装为map集合,将列名作为key,将值作为value 将这条记录封装为一个map集合
* 注意:这个方法查询的结果集长度只能是1queryForList()
:查询结果将结果集封装为list集合
* 注意:将每一条记录封装为一个Map集合,再将Map集合装载到List集合中query()
:查询结果,将结果封装为JavaBean对象
* query的参数:RowMapper
* 一般我们使用BeanPropertyRowMapper实现类。可以完成数据到JavaBean的自动封装
* new BeanPropertyRowMapper<类型>(类型.class)queryForObject
:查询结果,将结果封装为对象
* 一般用于聚合函数的查询
具体实现步骤
1.导入jar包
略
2.创建JdbcTemplate对象
由于创建JdbcTemplate对象依赖于数据源DataSource,所以使用C3P0+匿名对象创建JdbcTemplate对象
JdbcTemplate template = new JdbcTemplate(new ComboPooledDataSource());
3.不同方法介绍
update();这个方法有许多重载
例如
String sql = "update money set moneys=50 where id=1";
int update = template.update(sql);
或者
String sql = "update money set moneys=50 where id=?";
int update = template.update(sql, 2);
queryForMap();方法
String sql2="SELECT * FROM money WHERE id = 2";
Map<String, Object> map = template.queryForMap(sql2);
// 遍历map集合
Set<String> strings = map.keySet();
for (String key : strings) {
System.out.println( key+"="+map.get(key));
}
queryForList();方法
//3.3 queryForList方法
String sql3="SELECT * FROM money ";
List<Map<String, Object>> list = template.queryForList(sql3);
// 遍历list集合
// 获取迭代器对象
ListIterator<Map<String, Object>> iterator = list.listIterator();
while (iterator.hasNext()){
Map<String, Object> map1 = iterator.next();
System.out.println(map1);
}
query();方法
使用该方法,需要传递两个参数sql语句和RowMapper接口
RowMapper接口:这个接口要求实现public <E> mapRow(ResultSet rs, int i)
方法,我们可以选择自定义实现该接口,也可以选择已经实现好的类去实现
选择自定义实现该接口
/ 3.4 query():查询结果,将结果封装为JavaBean对象
String sql4="SELECT * FROM money ";
List<money> query = template.query(sql4, new RowMapper<money>() {
@Override
public money mapRow(ResultSet resultSet, int i) throws SQLException {
money money = new money();
/ 取出但个数据
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
double moneys = resultSet.getDouble(3);
/ 封装到money对象中去
money.setId(id);
money.setName(name);
money.setMoney(moneys);
return money;
}
});
System.out.println(query);
选择已经实现好的类去实现,直接调用BeanPropertyRowMapper方法,需要传递泛型,以及泛型的字节码文件;
如下
BeanPropertyRowMapper<money>(money.class));
// 3.4 query():查询结果,将结果封装为JavaBean对象
String sql5="SELECT * FROM money ";
List<money> query1 = template.query(sql5, new BeanPropertyRowMapper<money>(money.class));
System.out.println(query1);
queryForObject();方法
//4. queryForObject:查询结果,将结果封装为对象
String sql6 = "select count(ID) from money";
Integer integer = template.queryForObject(sql6, Integer.class);
System.out.println(integer);
全部代码
public class JdbcTemplates {
public static void main(String[] args) {
//2. 创建JdbcTemplate对象。
JdbcTemplate template = new JdbcTemplate(new ComboPooledDataSource());
String sql1 = "update money set moneys=50 where id=1";
//3. 调用JdbcTemplate的方法来完成CRUD的操作
//3.1 update方法
int update = template.update(sql1); // 返回影响的行数
System.out.println(update);
//3.2 queryForMap方法
String sql2="SELECT * FROM money WHERE id = 2";
Map<String, Object> map = template.queryForMap(sql2);
// 遍历map集合
Set<String> strings = map.keySet();
for (String key : strings) {
System.out.println( key+"="+map.get(key));
}
//3.3 queryForList方法
String sql3="SELECT * FROM money ";
List<Map<String, Object>> list = template.queryForList(sql3);
// 遍历list集合
// 获取迭代器对象
ListIterator<Map<String, Object>> iterator = list.listIterator();
while (iterator.hasNext()){
Map<String, Object> map1 = iterator.next();
System.out.println(map1);
}
// 3.4 query():查询结果,将结果封装为JavaBean对象
String sql4="SELECT * FROM money ";
List<money> query = template.query(sql4, new RowMapper<money>() {
@Override
public money mapRow(ResultSet resultSet, int i) throws SQLException {
money money = new money();
// 取出但个数据
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
double moneys = resultSet.getDouble(3);
// 封装到money对象中去
money.setId(id);
money.setName(name);
money.setMoneys(moneys);
return money;
}
});
System.out.println(query);
System.out.println("***********************");
// 3.4 query():查询结果,将结果封装为JavaBean对象
String sql5="SELECT * FROM money ";
List<money> query1 = template.query(sql5, new BeanPropertyRowMapper<money>(money.class));
System.out.println(query1);
//4. queryForObject:查询结果,将结果封装为对象
String sql6 = "select count(ID) from money";
Integer integer = template.queryForObject(sql6, Integer.class);
System.out.println(integer);
}
}