JDBC学习第二天
事务
简介
事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。是一系列操作,这些操作作为整体向系统提交,要么都执行,要么都不执行,事务是一个不可分割的工作逻辑单元。
例如银行转账:张三给李四转账500块钱,需要完成两个操作,张三减500,李四加500,当张三出现异常时,没有减成功,而李四却加了500,这时候银行就会亏损。
事务的特性
事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
mysql中事务的使用
开始事务:START TRANSACTION
提交事务:COMMIT
回滚事务(撤销事务):ROLLBACK
案例:
(1)我们创建一个银行表
CREATE TABLE BANK(
NAME VARCHAR(50),
MONEY INT
);
(2)插入两行数据
INSERT INTO BANK VALUES('ZHANGSAN',1000);
INSERT INTO BANK VALUES('LISI',1000);
(3)开始事务
START TRANSACTION;
(4)执行转账操作
UPDATE BANK SET MONEY=MONEY-100 WHERE NAME='ZHANGSAN';
UPDATE BANK SET MONEY=MONEY+100 WHERE NAME='LISI';
(5)提交或者回滚事务
COMMIT (提交事务)
ROLLBACK(回滚事务):刚才做的所有操作都取消
JDBC操作事务
Connection中有三个方法和事务相关
-setAutoCommit(boolean):设置是否自动提交事务,默认为true,每条语句都是一个单独的事务。设置false,那么就相当于开始事务,不自动提交,需要我们提交。
-commit():提交事务
-rollback():回滚事务
案例:
public class Demo4 {
public static void main(String[] args) throws SQLException {
Connection conn=null;
try {
conn=JdbcUtils.getConnection(); //获取连接
String sql1="UPDATE BANK SET MONEY=MONEY-100 WHERE NAME='zhangsan'";
String sql2="UPDATE BANK SET MONEY=MONEY+100 WHERE NAME='lisi'";
Statement st=conn.createStatement();
conn.setAutoCommit(false); //开启事务
st.executeUpdate(sql1); //执行语句
st.executeUpdate(sql2);
conn.commit(); //提交事务
} catch (Exception e) {
conn.rollback(); //如果出现异常则回滚事务
}
}
}
事务并发引起的问题
-脏读,某个事务读取的数据是另一个事务正在处理的数据,而另一个事务可能会回滚,造成第一个事务读取的数据是错误的。
-不可重复读:当一个事务读取某一数据后,另一事务对该数据执行了更新操作,使得第一个事务无法再次读取和前一次一样的数据。
-幻读:例如第一个事务对一个表中的所有行进行修改,第二个事务是向表中插入一行数据,这时操作第一个事务的用户就会发现还有没修改的行,像发生了幻觉。
事务隔离级别
-SERIALIZABLE(串行化):不会出现任何并发问题,因为对数据的访问是串行化,而不是并发访问。能解决上面三个问题,但性能最差,没人用。
-REPEATABLE READ(可重复读):可以防止脏读和不可重复读的问题
-READ COMMITTED(读已提交数据):可以防止脏读问题,性能比REPEATABLE READ好
-READ UNCOMMITTED:不能解决任何问题。性能最好。没人用
Mysql默认隔离级别为REPEATABLE READ 。
可以通过 SELECT @@tx_isolation 来查看隔离级别。
可以通过 set transaction isolationlevel[四个选一个]来设置隔离级别
JDBC设置隔离级别
connection.setTransactionisolation(int level)
参数:
-Connection.TRANSACTION_READ_UNCOMMITTED
-Connection.TRANSACTION_READ_COMMITTED
-Connection.TRANSACTION_REPEATABLE_READ
-Connection.TRANSACTION_SERIALIZABLE
数据库连接池
概念
数据库连接池负责分配,管理和释放数据库连接,允许应用程序重复使用一个现有的数据库连接。
而不是再重新建立一个连接,能明显提高对数据库操作的性能。
当我们每次需要操作数据库时,如果我们每次调用方法都要新建一个连接,那样会大大影响程序的性能。
而我们只需要将创建好的连接对象放在连接池中,用时取出来,不用就再归还。这样能明显提高性能。
原理
连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。
使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。
同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等。也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。
连接池基本参数
初始大小:初始拥有的连接对象个数
最小空闲连接数:当我们的连接对象都被使用时,剩余的连接对象到达最小空闲连接数时,就要再创建一些连接对象。
最大连接数:如果数据库连接请求超过此数,后面的数据库请求将被加入等待队列中。
还有很多的连接池参数,我们可以百度进行查询设置。
连接池特性
连接池的close()方法不是销毁连接,而是把当前连接归还给连接池。
java使用数据库连接池
数据库连接池必须实现DataSource接口。
数据库连接池有很多开源的项目,不用我们来实现。
我们来学习几个常用的连接池。
dbcp连接池。
由apache提供。使用时需要导入commons-dbcp.jar,commons-pool.jar和 commons-logging.jar 三个jar包。
操作步骤:
1.导入jar包
2.创建连接池对象
3.设置连接的四大参数 (驱动名字,url,数据库名字,数据库密码),我们也可以设置一些连接池的参数
4。调用方法获取连接
public class Demo5 {
public static void main(String[] args) throws SQLException {
BasicDataSource datasource=new BasicDataSource(); //创建连接池对象
datasource.setDriverClassName("com.mysql.jdbc.Driver"); //设置四大参数
datasource.setUrl("jdbc:mysql://localhost:3306/mysql1");
datasource.setUsername("root");
datasource.setPassword("123456");
Connection conn=datasource.getConnection(); //获取连接
}
}
c3p0连接池
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。
操作步骤:
1.导入jar包(c3p0.jar 和 mchange-commons-java.jar)
2.创建连接池对象
3.设置连接的四大参数参数(驱动名字,url,数据库名字,数据库密码),我们也可以设置连接池参数
4.调用方法获取连接
public class Demo6 {
public static void main(String[] args) throws PropertyVetoException, SQLException {
ComboPooledDataSource datasource=new ComboPooledDataSource(); //创建连接池对象
datasource.setDriverClass("com.mysql.jdbc.Driver"); //设置四大参数
datasource.setJdbcUrl("jdbc:mysql://localhost:3306/mysql1");
datasource.setUser("root");
datasource.setPassword("123456");
Connection conn=datasource.getConnection(); //获取连接
System.out.println(conn);
conn.close(); //归还连接
}
}
使用c3p0配置文件设置数据库连接四大参数,和连接池参数
首先,必须在本类所在路径创建一个 c3p0-config.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config> //默认配置
<property name="user">root</property> //数据库名字
<property name="password">123456</property> //数据库密码
<property name="driverClass">com.mysql.jdbc.Driver</property> //驱动名字
<property name="jdbcUrl">jdbc:mysql://localhost:3306/mysql1</property> //url
<property name="initialPoolSize">10</property> //连接池参数
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">10</property>
</default-config>
<named-config name="myconfig"> //不同的数据库参数。使用时需要用到name这个参数
<property name="user">root</property>
<property name="password">java</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc</property>
<property name="initialPoolSize">10</property>
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">10</property>
</named-config>
</c3p0-config>
案例:使用配置文件获取连接
public class Demo7 {
public static void main(String[] args) throws SQLException {
ComboPooledDataSource datasource=new ComboPooledDataSource();//创建连接池对象
Connection conn=datasource.getConnection(); //获取连接,配置都在配置文件中
System.out.println(conn);
}
}
配置文件里可以写多个不同的数据库配置,使用时需要传入name参数。
例如:使用上面配置文件中name=myconfig的配置
public class Demo7 {
public static void main(String[] args) throws SQLException {
ComboPooledDataSource datasource=new ComboPooledDataSource("myconfig"); //参数传入使用的配置的名字
Connection conn=datasource.getConnection();
System.out.println(conn);
}
}
ThreadLocal 类
简介
泛型类。
ThreadLocal翻译过来是“本地线程”,其实ThreadLocal并不是线程,而是线程的局部变量。相当于一个容器。用来存取变量。
使用ThreadLocal操作变量时,ThreadLocal会为使用该变量的线程提供独立的副本,而不会影响其他线程对应的副本。
ThreadLocal 不是用来解决共享对象的多线程访问问题的。
一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。
各个线程中访问的是不同的对象。
简单的说,A和B共享一个桌子,是不能通过ThreadLocal解决的,因为ThreadLocal为A和B一人给了个桌子。
ThreadLocal解决的只是为了结构上需要而共享资源,其实根本没必要共享资源的情况。
原理
ThreadLocal是如何做到为每一个线程维护变量的副本?
在Threadlocal类中有一个Map集合,Map中元素键为当前线程对象,值则对应副本的值。
1.每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
2。将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。
我们大概实现一下ThreadMap
public class ThreadLocalTest<T> {
private Map<Thread,T> map=new HashMap<Thread,T>(); //内部有一个map对象
public void set(T data)
{
map.put(Thread.currentThread(), data); //将当前线程对象作为键,我们传入的参数设为值
}
public T get()
{
return map.get(Thread.currentThread()); //获取当前线程的值
}
public void remove()
{
map.remove(Thread.currentThread()); //移除当前线程
}
}
ThreadLocal案例
public class ThreadLocal1 {
public static void main(String[] args) {
final ThreadLocal <String>tl=new ThreadLocal<String>(); //创建ThreadLocal
tl.set("hello"); //设置值
new Thread(){ //开启一个新线程
public void run(){
System.out.println(tl.get()); //获取不到设置的值,输出为null
}
}.start();
System.out.println(tl.get()); //输出hello
}
}
DBUtils学习
简介
Commons DbUtils是Apache组织提供的一个对JDBC进行简单封装的开源工具类库。
使用它能够简化JDBC应用程序的开发,同时也不会影响程序的性能。
为什么使用DBUtils?
在之前,我们对数据库的操作都是通过JDBC来实现的,我们需要获取连接,设置sql模板,给sql模板设置值,然后再执行语句。
这样我们会写大量重复的代码,操作复杂,效率低。
自己简化一下JDBC繁琐的操作
(1)我们首先创建一个数据表
四列,分别是id,名字,性别,年龄
create table stu(
id char(10),
name varchar(50),
gender varchar(10),
age int
)
(2)创建一个Stu类,是一个Javabean,用来对stu进行封装
stu类。javabean。对应数据库里stu表的 id,name,gender,age。
用来对stu进行封装。
public class stu {
private String Id;
private String Name;
private String Gender;
private int Age;
public stu()
{
}
public stu(String id, String name, String gender, int age) {
super();
Id = id;
Name = name;
Gender = gender;
Age = age;
}
public String getId() {
return Id;
}
public void setId(String id) {
Id = id;
}
public String getName() {
return Name;
}
public void setName(String name) {
Name = name;
}
public String getGender() {
return Gender;
}
public void setGender(String gender) {
Gender = gender;
}
public int getAge() {
return Age;
}
public void setAge(int age) {
Age = age;
}
}
(3)我们写一个接口,用来对结果集进行操作。具体要怎么操作由调用者自己实现。
interface RsHandler<T> {
public T handle(ResultSet rs)throws SQLException;
}
(4)我们写一个工具类对JDBC的操作进行简化
public class MyDbutils<T> {
private DataSource datasource;
public MyDbutils(){ //无参构造方法
}
public MyDbutils(DataSource datasource) //我们在构造方法中传入一个连接池对象
{
super();
this.datasource=datasource;
}
public int update(String sql,Object... params) //对数据库进行增删改操作,参数为sql模板和操作数据库需要的参数
{
Connection conn=null;
PreparedStatement ps=null;
try {
conn=datasource.getConnection(); //通过传入的连接池获取连接
ps=conn.prepareStatement(sql);
initParams(ps,params); //调用给sql模板赋值的方法
return ps.executeUpdate(); //执行操作并返回影响的行数
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
try{
if(ps!=null) ps.close();
if(conn!=null) conn.close();
}catch (Exception e) {
}
}
}
public void initParams(PreparedStatement pst,Object...params) throws SQLException //给sql模板赋值的方法,传入PraparedStatement对象和一个参数数组
{
for(int i=0;i<params.length;i++)
{
pst.setObject(i+1, params[i]); //进行遍历赋值
}
}
public T query(String sql,RsHandler rh,Object...params) //对数据库进行查询操作,参数为 sql模板,对结果集进行操作的接口,sql语句需要的参数
{
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try {
conn=datasource.getConnection();
ps=conn.prepareStatement(sql);
initParams(ps,params);
rs=ps.executeQuery(); //执行查询语句,得到结果集
return (T) rh.handle(rs); //将结果集交给接口的处理方法,方法我们不写,由调用者处理
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
try{
if(rs!=null) rs.close();
if(ps!=null) ps.close();
if(conn!=null) conn.close();
}catch (Exception e) {
}
}
}
}
(5)我们来测试一下我们写的工具类
public class Test {
public static void main(String[] args) {
/*stu s=new stu();
s.setId("0004");
s.setName("蓝猫");
s.setGender("man");
s.setAge(56);
addStu(s);*/ //调用addStu方法,将stu对象插入数据库
stu s=load("0002"); //调用查询方法,并返回stu对象
System.out.println(s.getAge());
}
public static void addStu(Stu stu) //我们向数据库插入一个stu对象
{
DataSource datasource=JdbcUtils.getDataSource(); //获取连接池
MyDbutils md=new MyDbutils(datasource); //实例化我们自己写的工具类,传入连接池
//调用update方法,传入sql模板,参数数组
md.update("insert into stu values(?,?,?,?)", new Object[]{stu.getId(),stu.getName(),stu.getGender(),stu.getAge()});
}
public static stu load(String id) //我们通过ID查询数据库,返回一个stu对象
{
DataSource datasource=JdbcUtils.getDataSource();
MyDbutils md=new MyDbutils(datasource);
RsHandler <stu>rh=new RsHandler<stu>(){ //写一个实现接口的内部类,用来对结果集进行操作
public stu handle(ResultSet rs) throws SQLException {
if(!rs.next()) return null; //将结果集封装成一个对象返回
stu s=new stu();
s.setId(rs.getString("ID"));
s.setName(rs.getString("NAME"));
s.setGender(rs.getString("GENDER"));
s.setAge(rs.getInt("AGE"));
return s;
}
} ;
return (stu) md.query("select * from stu where id=?", rh,new Object[]{id}); //调用query方法,传入sql模板,具体的实现类,参数数组
}
}
我们可以看到我们对数据库的操作已经简化了很多,省去了很多重复的代码,比如设置参数值,关闭资源等。
其实Apache提供的DBUtils类和我们做的差不多,我们相当于自己实现了一个简易的DBUtils。
但是DBUtils提供了很多对结果集进行操作的实体类。不需要我们再自己来实现。
DBUtils的使用
DButils主要类
QueryRunner类
-update方法
*int update(String sql,Object... params):可以执行增删改操作,用实例化QueryRunner时传入的连接池获取连接。
*int update(Connection con,String sql,Object... params):不用连接池的连接,自己提供连接。这样可以保证使用的是同一个连接。我们用到事务的操作时,一般用这个。、
-query方法
*T query(String sql,ResultSetHandler rsh,Object... params):可以执行查询操作。
会先查询得到结果集,然后调用ResultSetHandler的handle()方法 把结果集转换成需要的类型。
*T query(Connection con,String sql,ResultSetHandler rsh,Object... params):自己传入connection。需要进行事务操作时使用。
ResultSetHandler接口
用于把结果集进行转换的接口。
我们可以实现该接口自己来实现对结果集的操作。但apache提供了很多这个接口的实现类。
DBUtils使用小案例
public class DbutilsTest {
public static void main(String[] args) throws SQLException {
//update(); //调用对数据库增删改操作的方法
//query(); //调用对数据库进行查询的方法
}
public static void update() throws SQLException //对数据库进行增删改等操作
{
QueryRunner qr=new QueryRunner(new JdbcUtils().getDataSource()); //获取DBUtils提供的工具类对象
String sql="insert into stu values(?,?,?,?)"; //设置sql模板
Object[] params={"1008","康熙","man","1000"}; //设置参数数组
qr.update(sql, params); //调用update方法,传入sql模板和参数数组
}
public static stu query() throws SQLException //对数据库进行查询操作,并返回一个对象
{
QueryRunner qr=new QueryRunner(new JdbcUtils().getDataSource());
String sql="select * from stu where id=?";
Object[] params={"1003"};
ResultSetHandler<stu> rsh=new ResultSetHandler<stu>(){ //我们需要实现这个接口来对查询出来的结果集进行操作,和我们自己写的几乎一样
public stu handle(ResultSet arg0) throws SQLException {
// TODO Auto-generated method stub
return null;
}
};
return qr.query(sql, params, rsh); //调用query方法,传入sql模板,对结果集操作的实现类,参数数组
}
}
Apache提供的ResultSetHandler接口的实现类
DBUtils提供了很多的ResultSetHandler接口的实现类,供我们对结果集进行操作,不用我们再自己写实现类。
(1)BeanHandler类
需要一个javabean的class文件,会将结果集中的数据封装到该javabean对象中,然后返回该javabean对象
需要注意的是javabean中的成员变量必须和数据库中的列名称一样。
public class DbutilsTest {
public static void main(String[] args) throws SQLException {
QueryRunner qr=new QueryRunner(new JdbcUtils().getDataSource());
String sql="select * from stu where id=?"; //sql模板
Object[] params={"0003"}; //参数数组
stu s=qr.query(sql, new BeanHandler<stu>(stu.class), params); //提供stu类的class,返回一个stu对象
System.out.println(s.getName());
}
}
(2)BeanListHandler类
多行。需要一个指定的class类型的参数,用来把结果集转换成list对象,list集合里是每一行的javabean对象。
public class DbutilsTest {
public static void main(String[] args) throws SQLException {
QueryRunner qr=new QueryRunner(new JdbcUtils().getDataSource());
String sql="select * from stu "; //sql模板,对所有行进行查询
List<stu> l=qr.query(sql, new BeanListHandler<stu>(stu.class));//返回list集合
System.out.println(l);
}
}
(3)MapHandler类
单行。把一行结果转换成map对象。
public class DbutilsTest {
public static void main(String[] args) throws SQLException {
QueryRunner qr=new QueryRunner(new JdbcUtils().getDataSource());
String sql="select * from stu where id=?";
Object[] params={"0001"};
Map l= qr.query(sql, new MapHandler(),params);//返回一个Map对象
System.out.println(l);
}
}
(4)MapListHandler类
多行。把多行记录转换成多个Map。即list<Map>。
public class DbutilsTest {
public static void main(String[] args) throws SQLException {
QueryRunner qr=new QueryRunner(new JdbcUtils().getDataSource());
String sql="select * from stu ";
List<Map<String, Object>> l= qr.query(sql, new MapListHandler()); //返回一个list<Map>集合对象
System.out.println(l);
}
}
Apache还给我们提供了很多这种实现类,我们可以在查询使用。我写的并不全面,可以参考网上的文章进行深入学习