java类和数据库表的关系
类对应表,字段对应属性,一行数据对应一个对象,所以类的属性名要和表的字段名相同才能进行增删改查
JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。jdbc:java是通过jdbc技术实现对各种数据库的访问的,换句话说,jdbc是java应用程序与各种数据库之间进行对话的媒介,因此实际开发中用JDBC链接数据库后通过JDBC发送sql语句就可以操作数据库。
为什么使用JDBC
以前连接数据库的方式:如果你想要连接某个数据库,就必须连接这个数据库的API,
缺点:不是跨平台的,如果想换数据库需要重新编写代码
如今的连接数据库方式:不需要掌握特定数据库的API,程序员只需用JDBC API写一个程序就够了,它可向相应数据库发送SQL调用,同时,将Java语言和JDBC结合起来使程序员不必为不同的平台编写不同的应用程序,只须写一遍程序就可以让它在任何平台上运行,这也是Java语言“编写一次,处处运行”的优势。
但是每一种数据库JDBC的类库是不同的,所以每一种数据库都提供了其专有的JDBC
三层应用模型,在三层引用模型中,客户端不直接调用数据库,而是调用中间层也就是JDBC完成数据库的查询工作
JDBC主要类和接口
一个Statement只能有一个打开的结果集,所以当你希望打开两个结果集,就要创建两个statement对象
一个Statement对象同时只能有一条语句结果集在活动.这是宽容性的,就是说即使没有调用ResultSet的close()方法,只要打开第二个结果集就隐含着对上一个结果集的关闭.所以如果你想同时对多个结果集操作,就要创建多个Statement对象,如果不需要同时操作,那么可以在一个Statement对象上须序操作多个结果集.
可滚动可更新的结果集ResultSet (了解)
不建议使用因为有的数据库不支持,这就违背了数据库的可移植性
默认情况下结果集不可以滚动,所以需要建立一个不一样的传输器对象
Statement stat=conn.Statement(type,concurrency)
PreparedStatement ps=conn.PreparedStatement(conmand,type,concurrency)
type:
ResultSet.TYPE_SCROLL_INSENSITIVE, 可以滚动,但对数据库变化不敏感(常用)
ResultSet.TYPE_SCROLL_SENSITIVE 可滚动,对数据库变化敏感
concurrency:
ResultSet.CONCUR_READ_ONLY 并发访问只能够读取内容,不可用于 更改表(常用)
ResultSet.CONCUR_UPDATETABLE 并发访问可以通过更改结果集更改表
可滚动不可更新结果集
(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY)
ResultSet rs=ps.executeQuery()
此时这个结果集rs是滚动的
常用方法
rs.next()
rs.last()
rs.isLast()
rs.getRow() 返回当前记录是第几条记录(当前行行号)
rs.previous()
rs.relative(n) 向前或向后移动多行 n为正数向前移动,n为负数向后移动
rs.absolute(int a) 获取指定位置的记录
可滚动可更新结果集(适用用户任意修改得到的结果集数据的程序,大多数程序不建议用)
(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_UPDATETABLE)
行集(了解)
1.基于结果集的缺点:在与用户的整个交互过程中,必须始终与数据库保持连接
后果:当用户长时间离开时,数据库连接长时间被占用,而这属于稀缺资源;
解决:使用行集RowSet,RowSet继承了ResultSet接口,却无需始终保持与数据库的连接~
2.结果集不便于移动,因为数据结构复杂,且依赖于连接
解决:使用行集RowSet,RowSet适用于将查询结果移动到复杂应用的其他层,或者其他设备当中
3.因为RowSet继承了ResultSet接口,所以ResultSet中的方法RowSet都能用,RowSet是对ResultSet离线化、移动化的增强
sql注入
select * from t_user where username='张三' and password='2' or '1'='1'
在使用Statement的时候,由于这里属于硬编码,也就是只要满足sql语句的查询条件为true就能查询出数据,但是查询条件不是我们给定的,这种情况就是sql注入
解决方法
使用Statement的子接口PreparedStatement,它可以对sql语句进行预编译
SQL语句被预编译并存储在PreparedStatement对象中。然后可以使用此对象多次高效地执行该语句。
预编译
通过Statement对象执行sql语句时,需要将sql语句发送给DBMS(数据库管理系统),由DBMS首先进行编译再执行(在创建通道的时候并不进行sql的编译工作,事实上也无法进行编译)。而通过PreparedStatement不同,在创建PreparedStatement对象时就指定了sql语句,该语句立即发送给DBMS进行编译,当该语句被执行时,DBMS直接运行编译后的sql语句,而不需要像其他sql语句那样首先将被编译。
1.提高了效率,因为在运行前不需要再进行一次编译(批处理),SQL语句被预编译并存储在PreparedStatement对象中。然后可以使用此对象多次高效地执行该语句。
2.提高了安全性(防止sql注入),编译的时候就明确了查询条件,之后每一次传入值,只会把传入的值和表里的字段进行匹配。
3. PreparedStatement可以写动态参数化的查询,用 ? 代替参数的时候不需要明确参数类型,所以在运行的时候传入任何类型的参数都可以
注:Statement主要用于执行静态SQL语句,即内容固定不变的SQL语句。所以以后写程序99%用PreparedStatement
为什么它这样处理就能预防SQL注入提高安全性呢?
其实是因为SQL语句在程序运行前已经进行了预编译,在程序运行时第一次操作数据库之前,SQL语句已经被数据库分析,编译和优化,对应的执行计划(也就是确定了要执行的sql语句)也会缓存下来并允许数据库以参数化的形式进行查询,当运行时动态地把参数传给PreprareStatement时,即使参数里有敏感字符如 or '1=1'数据库也会把它作为一个参数一个字段的值去表格中寻找与它相等的字段值然后进行sql语句操作而不会作为一个SQL的条件指令,如此,就起到了SQL注入的作用了!
Statement是调用者写sql语句,而PreparedStatement是调用者赋参数值,然后参数值去表中指定字段中进行比较,
所以前者调用者可能在where后面写一个永远为true的语句,这样就能把所有的说句查询出来,当这个语句是判断账号密码是否正确的时候,因为永远为true,所以调用者跳过了账号密码判断是否正确的步骤,直接登陆了进去,而后者调用者只能赋参数,必须去表中的指定字段中寻找,只有账号密码都满足的时候,才登陆了进去。
Statement是满足条件就执行sql操作,而PreparedStatement是只有找到与指定字段相等的值的时候才进行sql操作
建立传输器
String sql=“select * from t_user where username=? and password=?”
PreparedStatement ps=conn.prepareStatement(sql)
这个sql的意义是进行一次预编译,所谓预编译就是提前判断该sql语句是否正确,其中 ?的作用是占位符,占位符的索引是从1开始
给占位符赋值
ps.setString(第一个问号位置(1),赋的值)
ps.setString(1,"张三")
ps.setString(2,“123”)
执行sql语句
缺点:每一次只能预编译一条sql语句
单表查询时,你可以把查询的内容存到一个对象里,
多表联查的问题
在java当中进行多表联查的时候往往不知道返回什么类的数据,这个时候我们可有定义一个包装类来解决这样的问题
如果是多表查询,建立一个类,将两个表的类放到这个类里,查询的结果存到这个新构成的类
批量操作
无论是增加还是删除还是修改每做一次操作都需要连接一次数据库,这样频繁的与数据库进行交互的话会严重影响程序的性能,这个时候我们就需要使用批量操作例如向数据库中一次性的插入1000条数据
addBatch添加到缓冲区
executeBatch更新缓冲区
clearBatch清空缓冲区
获取未知的主键
插入的数据如果存在外键约束 或者 这个主键是自动生成的,你不知道这个字段值,而你又想得到这个值并把它插到另一个表里,先要在提供约束的表里插入数据,然后获取主键值,再将输入连带主键值插入到被约束的表
注:最好一个表不要有外键约束,而用逻辑判断实现外键约束功能,因为外键约束影响性能
ps.getGeneratedKeys()获取插入语句插入完成后自动生成的主键值
sql转义
1.时间日期函数,java里的Date和sql的Date表现形式不同,如果你想要把java里的Date存到数据库里
使用new java.sql.Date(new java.util.Date().getTime());进行日期的转义
所以在链接数据库的时候建议导入sql包下的Date
如何解决,键盘输入日期对象存到数据库里呢?
(1)键盘输入字符串转换为util包下的Date,使用了格式化类
SimpleDateFormat sdf=new SimpleDateFormat("yyyy,MM,dd");表示字符串是必须按照这个形式输入的
java.util.Date date= sdf.parse(s); //将字符串s转换为了util下的Date对象
(2)然后将util包下的Date转换为sql包下的Date然后导入到数据库
new Date(date.getTime());
2.数据库中某个基本类型的字段值为null,当把这个值传给类时,类中的基本类型不可以为null,就会报错,所以数据库和java相连的时候,把基本类型设置为其包装类类型(少用,因为数据库内的null我们想让它表示的是0)或者是用ifnull(可能为null的字段值,0)函数将null字段值替换为0
连接池(c3p0和dbcp) 速度块
DateSource数据源用于替代DriverManger获取数据库连接,主要用于连接池的使用(效率高)
如果一个项目中需要多个连接,如果一直获取连接,断开连接,这样比较浪费资源,如果创建一个池,用池来管理Connection,这样就可以重复使用Connection。有了池我们就不用自己来创建Connection,而是通过池来获取Connection对象。当使用完Connection后,调用Connection的close()方法也不会真的关闭Connection,而是把Connection“归还”给池。池就可以再利用这个Connection对象了。这里我们常用的连接池有两种,分别是:DBCP连接池和C3P0连接池,下边是对两种连接池的具体使用和比较。
dbcp连接池(spring开发组推荐使用dbcp)
引入包
commons-dbcpxxx.jar
commons-poolxxx.jar
在使用写连接池工具类的时候,可以通过配置文件来连接数据库,配置文件中记录了连接数据库的驱动、URL、用户名和密码等信息,但要注意这里的文件后缀为:“.properties”。把通过配置文件连接数据库的部分写在静态代码块中,随着类的加载只加载一次。除了连接数据库,还要提供一个获得数据源的方法和一个获取连接的方法,写成工具类和直接使用的代码几乎相同,只是增强了代码的复用性。
c3p0连接池(hibernate开发组推荐使用c3p0;)稳定
c3p0-0.9.1-pre5.jar如果这个包是9.2以后的还要另外导入mchange-commons-java
事务:
银行转账问题,A,B各有两千块钱,
A要转给B500,这是先从A的账户扣除500,在给B账户增加500,如果在扣除A 500之后和增加B 500之前出现了问题,那么就会产生A扣钱了,但是B却没增加钱的问题,这时候就需要一个东西,让扣钱和增钱只有都完成了这个操作才会被执行
事实上mysql里每一条sql语句都是在运行后被提交执行的(每执行一条sql都是执行一条事务),运行不会改变数据库只会显示结果,而提交才改变了数据库,而mysql是自动执行提交的
try{
con.setAutoCommit(fasle); //设置mysql事务不是直接提交的
....
....
con.commit(); //多条sql语句都执行完后一起提交,数据库表才会正式更改
con.setAutoCommit(true) //将自动提交改回
}catch(){
con.rollback(); //一旦其中出现错误就会回滚,都不提交,返回没执行sql语句执行状态
con.setAutoCommit(true) //将自动提交改回
}
事务的特性
事务有以下四个标准属性的缩写ACID,通常被称为:
原子性: 确保工作单元内的所有操作都成功完成,否则事务将被中止在故障点,和以前的操作将回滚到以前的状态。
一致性: 确保数据库正确地改变状态后,成功提交的事务。
隔离性: 使事务操作彼此独立的和透明的。
持久性: 确保提交的事务的结果或效果的系统出现故障的情况下仍然存在。
事务的隔离级别
并发读问题:
1.脏读:一个事务读到另一个事务未提交数据,就是脏读。(未提交不是实际更改,后期可能回滚,所以读的数据可能是假的)
比如:
2.不可重复读:一条事务读取某数据两次,第二次读取后才提交,两次中间有一条事务对数据进行更新,导致两次读的数据不一样。(一条事务在未提交前对某数据的查询结果应该是一致的)重点为:字段值的改变
比如:
3.幻读:对同一张表的两次查询不一致,因为另一事务插入了一条数据(一条事务在未提交前对同一张表的查询结果应该是一致的)
重点为:数据数量的改变
比如:
不可重复读和幻读有什么区别呢?
不可重复读是读到了另一个事务的更新(重点是数据内容的修改)
幻读是读到了表的插入(重点是表中增加或删除了一条数据)
四大隔离级别:
四个等级的隔离级别,在除了隔离级别不同什么都相同的情况下处理结果是不同的,因为四种隔离级别对并发数据的处理能力是不同的。
1.SERIALIZABLE(串行化)
因为是串行化,所以不会出现任何问题,效率最差
2.REPEATABLE READ(可重复读)MySql默认
防止脏读和不可重复读,不能防止幻读
3.READ COMMITTED(读已提交数据)
防止脏读
4.READ UNCOMMITTED(读未提交数据)
可能出现任何问题,效率最好
JDBC设置隔离级别:
依旧通过Connection对象,使用方法setTransactionisolation[ int level]
参数可选为
事务与批处理结合应用可以实现,多条语句更新的时候,如果一条语句出现了问题,都不会进行插入了