学习笔记-jdbc

JDBC

jdbc用来java与数据库连接,需要一些包(数据库驱动和一些连接池和工具包)
jar包分享地址
链接:https://pan.baidu.com/s/1lEBeZ-KUkcxhb7kk1sRecQ
提取码:1111

使用JDBC
//为了解耦合需要将连接数据库的配置写到properties文件中
public void getConnection(){InputStream in=null;Connection connection =null;try{//这里的Test类可以随意换成当前类in=Test.class.getClassLoader.getResourceAsStream("jdbc.properties");Properties properties=new Properties();properties.load(in);//获取properties中的配置信息String user = properties.getProperty("user");//数据库用户名String password = properties.getProperty("password");//数据库用户密码String url = properties.getProperty("url");//连接数据库urlString driverClass = properties.getProperty("driverClass");//驱动包Driver//加载驱动Class c=Class.forName(driverClass);//获取数据库连接不需要在注册驱动,在获取driverClass时内部自动注册connection = DriverManager.getConnection(url, user, password);}catch (Exception e){e.printStackTrace();}finally{try {connection.close();in.close();} catch (Exception e) {e.printStackTrace();}}
}
jdbc.properties中的内容
//jdbc.properties内容
user=root
password=123
url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true
driverClass=com.mysql.jdbc.Driver

这里连接mysql数据库,mysql是关系型数据库,将获取查询结果保存到对应的实体类(javabean)

javabean示例

ORM编程思想 (object relational mapping)
一个数据表对应一个java类
表中的一条记录对应java类的一个对象
表中的一个字段对应java类的一个属性

public class Customer {//类中的属性一一对应数据库表的列数据类型private int id;private String name;private String email;private Date birth;public Customer() {}public Customer(int id, String name, String email, Date birth) {this.id = id;this.name = name;this.email = email;this.birth = birth;}@Overridepublic String toString() {return "Customer{" +"id=" + id +", name='" + name + '\'' +", email='" + email + '\'' +", birth=" + birth +'}';}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}public Date getBirth() {return birth;}public void setBirth(Date birth) {this.birth = birth;}
}

对数据操作

基本上对数据库操作必须的步骤
第一步:获取数据库连接 Connection
第二部:获取执行sql语句的类对象 Statement或者PrepareStatement(sql语句可能会出现sql注入的问题,所以建议使用PrepareStatement,想了解Statement可自行搜索)
第三步:通过PrepareStatement进行调用,如果是查询操作会返回一个ResultSet对象,通过此对象可以获取数据库表数据
第四步:关闭数据库连接等一系列资源操作

jdbc工具类

在对数据库操作之前我们把固定的步骤给封装起来,岂不是方便很多,不用再每次都进行相同的操作

public class JDBCUtils {
//封装的获取connection连接的方法public static Connection getConnection() throws Exception{//直接通过ClassLoader类获取InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");Properties properties=new Properties();properties.load(resourceAsStream);String user = properties.getProperty("user");String password = properties.getProperty("password");String url = properties.getProperty("url");String driverClass = properties.getProperty("driverClass");//通过driver内部自动注册驱动Class.forName(driverClass);//获取连接Connection connection = DriverManager.getConnection(url, user, password);return connection;}//封装的关闭连接数据库的方法public static void closeResource(Connection conn, PreparedStatement ps, ResultSet rs){//每一处关闭操作都需要非空判断,有可能没有用到某个资源就调用了此方法try {if (conn!=null)conn.close();} catch (SQLException throwables) {throwables.printStackTrace();}try {if (ps!=null)ps.close();} catch (SQLException throwables) {throwables.printStackTrace();}try {if (rs!=null)rs.close();} catch (SQLException sqlException) {sqlException.printStackTrace();}}
}

工具类此时就封装好了,接下来写第一个sql操作插入数据
需要结合orm思想的javabean来操作

插入数据

 public void insert(Customer customer){Connection conn = null;PreparedStatement ps=null;try {//获取数据库连接conn=JDBCUtils.getConnection();//使用预编译的sql语句防止注入问题,id位主键自增,使用默认DEFAULTps = conn.prepareStatement("insert into customers(id,name,email,birth) values(DEFAULT,?,?,?)");//设置每个列需要插入的数据,占位符?是从索引1开始ps.setString(1,customer.getName());ps.setString(2,customer.getEmail());ps.setDate(3,customer.getBirth());int execute = ps.executeUpdate();System.out.println(execute);if (execute==1){System.out.println("插入数据成功");}} catch (Exception e) {e.printStackTrace();}finally {JDBCUtils.closeResource(conn,ps,null);}}

删除修改都是类似的操作,只是sql语句不同

查询操作
public void query() {Connection connection =null;PreparedStatement ps =null;ResultSet resultSet =null;List list=new ArrayList();//查询数据可能不止一条,按实际情况try {connection = JDBCUtils.getConnection();String sql="select id,name,email,birth from customers where id=?";ps = connection.prepareStatement(sql);ps.setInt(1,1);//执行并返回结果集resultSet = ps.executeQuery();//处理结果集//判断结果集下一行是否有数据,有数据返回true,指针下移,如果没有数据,返回false,指针不移动if (resultSet.next()){int id = resultSet.getInt(1);String name = resultSet.getString(2);String email = resultSet.getString(3);Date date = resultSet.getDate(4);Customer customer=new Customer(id,name,email,date);list.add(customer);System.out.println(customer);}}catch (Exception e){e.printStackTrace();}finally {JDBCUtils.closeResource(connection,ps,resultSet);}}

操作基本是大同小异的,查询是有返回结果集的ResultSet
此时已经将查询方法写出来,但是我们需要查询特定的数据每次都需要修改?占位符的数据和sql语句,这样每次调用都需要重新进行重复操作,那太麻烦了,我们来将查询封装起来,需要传入什么数据就直接传入值进行查询

//封装查询方法
//使用可变参数,参数需要的指定数据
//返回值是你查询的类型
public Customer queryCustomer (String sql,Object ...args){Connection connection =null;PreparedStatement ps =null;ResultSet resultSet =null;Customer customer=null;//返回的对象类try {connection = JDBCUtils.getConnection();ps = connection.prepareStatement(sql);//将每个可变参数数据传入PreparedStatement ,注意与占位符?的顺序for (int i=0;i

通用查询方法

上面将查询方法给封装了,不过,还是不够方便,因为只是针对一个表,接下来将是对所有表的查询方法的封装

所有表的查询操作封装

//泛型方法,可以接受任何数据类型对象//需要通过反射将数据表中的数据映射到对应的实体类,所以需要传入需要操作对象的Class对象,并且设置为泛型,规范传入的数据public  T query(Class c,String sql,Object ...args){Connection connection =null;PreparedStatement ps =null;ResultSet rs =null;T t=null;//接收结果集的对象try {connection = JDBCUtils.getConnection();ps = connection.prepareStatement(sql);for (int i=0;i

为什么使用泛型,因为此方法的封装是可以对所有javabean进行操作,所以返回类型并不能确定,所以使用泛型来代替将要操作的类型,参数的Class对象是来进行对需要的数据进行从数据库取值在赋值到对应的类中,而且需要传入泛型的Class对象,因为需要确定返回的类型是哪种类型,如果返回的是泛型,是不可以的(详细内容可以搜索泛型的规则,和泛型进行强转的规则)
这里返回特定的数据,如果需要返回所有查询结果,可以写成返回值为List<>使用泛型更安全,此处就不再写了

DAO包

在项目中创建一个DAO包专门用来进行数据库操作的实现类和接口
上面只是将方法进行了整体的封装,这次需要将方法封装到一个类中
使下次对方法的调用和外部对方法的调用更简单并且可以解耦合

//BaseDAO
//用来封装所有的基础操作,如查询修改等基本操作
public class BaseDAO {//优化操作,对于指定的表查询操作动态的创建此表对应的java对应类的运行时对象//泛型Class,传入的泛型是什么类型就必须查询对应关系类型的数据表private Class c=null;//静态代码块,继承此类的子类一创建对象就指定好对应的javabean对象的动态创建,不需要在每次方法参数中传入要创建的对象{//为什么用this,因为此处是子类调用的父类构造器,所以this代表子类对象Type genericSuperclass = this.getClass().getGenericSuperclass();ParameterizedType parameterizedType=(ParameterizedType) genericSuperclass;Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();//获取了父类的泛型参数c=(Class) actualTypeArguments[0];//泛型第一个参数}//此时不再是泛型方法,而是通过泛型类确定返回值的泛型public List getForList(Connection conn, String sql, Object ...args){PreparedStatement ps =null;ResultSet resultSet =null;try {ps = conn.prepareStatement(sql);for (int i=0;i ts = new ArrayList<>();//处理结果集while (resultSet.next()){//判断结果集下一行是否有数据,有数据返回true,指针下移,如果没有数据,返回false,指针不移动T t=c.newInstance();for (int i=0;i getAll(Connection conn);//返回数据表中的数据条目数Long getCount(Connection conn);//返回数据表中最大的日期Date maxDate(Connection conn);
}
//CustomerDAOImpl  接口实现类
//DAO: data access object 数据访问对象
public class CustomerDAOImpl extends BaseDAO implements CustomerDAO {@Overridepublic List getAll(Connection conn) {String sql="select name,email from customers";List forList = getForList(conn, sql);//调用父类方法,因为继承父类指定了泛型,所以会返回指定泛型类型,防止接收其他类型返回值return forList;}
}

BaseDAO使用泛型,规范一个数据访问只能访问指定的类型数据,把类对象写入静态代码块,通过继承就可以动态的创建指定类,不需要在手动创建并传入。好处就是将业务代码进行了封装,需要什么业务就直接调用,不在需要调用类中写大量逻辑,方便以后的修改和迭代。

返回自增主键

有时候业务需要将新增的数据返回其主键,方法有很多,比如再做一次sql查询,使用官方api更简单

public int update(Connection conn,String sql,Object ...args){PreparedStatement ps=null;try {//使用两个参数的PreparedStatement 方法,第二个参数为Statement全局常量ps=conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);for(int i=0;i
批量插入数据
public void test() throws Exception{Connection connection= JDBCUtils.getConnection();String sql="insert into test(name) values(?)";PreparedStatement ps = connection.prepareStatement(sql);//设置自动提交为false,等最后一起提交connection.setAutoCommit(false);for (int i=0;i<=10000;i++){ps.setString(1,"name"+i);//要使用批量插入需要在连接数据库的url地址中加入一个属性//rewriteBatchedStatements=true//将sql语句添加到Batchps.addBatch();//此处的判断根据实际情况if (i%500==0){//执行batchps.executeBatch();//清空batch,清除之前已经执行的sqlps.clearBatch();}}//最后统一提交connection.commit();JDBCUtils.closeResource(connection,ps);}

大量数据插入时,使用批量插入可以减少很多时间

数据库插入图片等二进制文件
public class BlobTest {//向数据表插入blob字段@Testpublic void test() throws Exception {Connection connection= JDBCUtils.getConnection();String sql="insert into customers(name,email,birth,photo) values(?,?,?,?)";PreparedStatement ps = connection.prepareStatement(sql);ps.setObject(1,"坤坤");ps.setObject(2,"zhang@qq.com");ps.setObject(3,"1992-09-08");//路径是存放文件的路径File file=new File("tupian.jpg");InputStream inputStream=new FileInputStream(file);ps.setBlob(4,inputStream);ps.execute();JDBCUtils.closeResource(connection,ps);}//查询customers中的blob字段@Testpublic void testBlob() throws Exception{Connection connection= JDBCUtils.getConnection();String sql="select photo from customers where id=?";PreparedStatement ps = connection.prepareStatement(sql);ps.setInt(1,20);ResultSet resultSet = ps.executeQuery();if (resultSet.next()) {//blob字段下载下来以文件方式保存本地Blob blob = resultSet.getBlob(1);InputStream binaryStream = blob.getBinaryStream();FileOutputStream fileOutputStream=new FileOutputStream("maomi.jpg");byte[] bytes=new byte[1024];int len;while ((len=binaryStream.read(bytes))!=-1){fileOutputStream.write(bytes,0,len);}if (binaryStream!=null){binaryStream.close();}if (fileOutputStream!=null){fileOutputStream.close();}}JDBCUtils.closeResource(connection,ps,resultSet);}
}

并不建议将二进制文件传入数据库,这样会耗费大量时间,可以将图片保存到服务器,数据库存储保存路径,通过保存路径获取需要的图片资源

sql事务

事务的ACID属性

  • 原子性(Atomicity):原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生要么都不发生
  • 一致性(Consistency):事务必须使数据库从一个一致性状态变换到另外一个一致性状态
  • 隔离性(Isolation):事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事物之间不能相互干扰
  • 持久性(Durability):持久性是指一个事务一旦被提交,他对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响

connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);//设置数据库隔离级别
READ_UNCOMMITTED
READ_COMMITTED
REPEATABLE_READ
SERIALIZABLE(串列化)

public void test() {Connection connection=null;try {connection= JDBCUtils.getConnection();//自动提交为falseconnection.setAutoCommit(false);String sql="update user_table set balance=balance-100 where user=?";updateTest(connection,sql,"AA");//System.out.println(10/0);String sql2="update user_table set balance=balance+100 where user=?";updateTest(connection,sql2,"BB");//如果没有错误提交数据connection.commit();}catch (Exception e){try {//报错之后进行事务的回滚,必须回滚,虽然设置自动提交为false但是数据库连接关闭时还是会提交connection.rollback();} catch (SQLException sqlException) {sqlException.printStackTrace();}e.printStackTrace();}finally {try {//修改其为自动提交数据,主要针对于使用数据库连接池使用//设置连接为false之后需要将自动提交设置回来,因为如果是连接池的话连接是重复使用的connection.setAutoCommit(true);} catch (SQLException sqlException) {sqlException.printStackTrace();}JDBCUtils.closeResource(connection,null);}}

1.什么叫数据库事务
事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态
一组逻辑操作单元:一个或多个dml操作

2.事务处理原则:保证所有事物都作为一个工作单元来执行,即使出现故障都不能改变这种执行方式。 当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久的保存下来 , 要么数据库管理系统将放弃所有的修改,整个事务回滚(rollback)到最初状态

3.数据一旦提交不可回滚
>DML默认情况下操作一旦执行,都会自动提交
setAutoCommit=false的方式取消DML自动提交
>默认在关闭连接时,会自动提交数据

数据库连接池技术

数据库连接池(Database Connection Pooling)在程序初始化时创建一定数量的数据库连接对象并将其保存在一块内存区中,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个;释放空闲时间超过最大空闲时间的数据库连接以避免因为没有释放数据库连接而引起的数据库连接遗漏。
即在程序初始化的时候创建一定数量的数据库连接,用完可以放回去,下一个在接着用,通过配置连接池的参数来控制连接池中的初始连接数、最小连接、最大连接、最大空闲时间这些参数保证访问数据库的数量在一定可控制的范围类,防止系统崩溃,使用户的体验好。

c3p0

c3p0需要配置xml文件



com.mysql.jdbc.Driverjdbc:mysql:///test?characterEncoding=utf8root12351010100502

public class C3P0 {//数据库连接池只提供一个即可private static ComboPooledDataSource cpds=new ComboPooledDataSource("hellc3p0");@Testpublic void c3p0Test() throws PropertyVetoException, SQLException {ComboPooledDataSource cpds = new ComboPooledDataSource();//配置可以在xml文件中配置,也可以在java代码中配置cpds.setDriverClass( "com.mysql.jdbc.Driver" ); //loads the jdbc drivercpds.setJdbcUrl( "jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=false&serverTimezone=UTC" );cpds.setUser("root");cpds.setPassword("zhanghao123");//通过设置相关的参数,对数据库连接池进行管理//设置初始时数据库连接池中的连接数cpds.setInitialPoolSize(10);//获取连接池中的连接Connection conn=cpds.getConnection();System.out.println(conn);//销毁数据库连接池//DataSources.destroy(cpds);}
}

dbcp

dbcp配置properties文件,此处就不再写,其他配置可自行查阅

//dbcp.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test?characterEncoding=utf8
username=root
password=123
//初始化连接个数
initialSize=10
public class Dbcp {//dbcp数据库连接池技术//使用配置文件连接数据库private static DataSource dataSource;static {Properties pros=new Properties();InputStream dbcpIn = ClassLoader.getSystemClassLoader().getResourceAsStream("dbcp.properties");try {pros.load(dbcpIn);} catch (IOException e) {e.printStackTrace();}try {dataSource = BasicDataSourceFactory.createDataSource(pros);} catch (Exception e) {e.printStackTrace();}}public void getDBCPConnection() throws SQLException {//任何一个连接池都有DataSource的实现类//创建了dbcp的数据库连接池BasicDataSource source=new BasicDataSource();//设置基本信息source.setDriverClassName("com.mysql.jdbc.Driver");source.setUrl("jdbc:mysql:///test?characterEncoding=utf8");source.setUsername("root");source.setPassword("zhanghao123");//还可以设置其他涉及数据库连接池管理的相关属性source.setInitialSize(10);source.setMaxActive(10);Connection connection = source.getConnection();System.out.println(connection);}
}

druid

druid配置properties文件

url=jdbc:mysql://localhost:3306/test?characterEncoding=utf8
username=root
password=123
driverClassName=com.mysql.jdbc.DriverinitialSize=10
maxActive=10
public class DruidTest {private static DataSource dataSource;static {Properties pros=new Properties();try {FileInputStream fileInputStream=new FileInputStream(new File("src/druid.properties"));pros.load(fileInputStream);dataSource = DruidDataSourceFactory.createDataSource(pros);} catch (Exception e) {e.printStackTrace();}}@Testpublic void test() throws Exception {Connection connection = dataSource.getConnection();System.out.println(connection);}
}
jdbc工具apach

apach提供的api就是封装好的与数据库交互的一些工具类,就像上面写好的一些封装方法

来看看怎么使用

public class QueryRunnerTest {@Testpublic void testInsert() throws Exception {QueryRunner runner=new QueryRunner();Connection connection = JDBCUtils.getConnection();String sql="insert into customers(name,email,birth) values(?,?,?)";int insertCount = runner.update(connection, sql, "蔡徐坤", "caixukun@126.com", "1997-01-09");System.out.println(insertCount);JDBCUtils.closeResource(connection,null);}//queryRunner查询//BeanHandler:是ResultSetHandler接口的实现类,用于封装表中的一条记录@Testpublic void testQ() throws Exception {QueryRunner runner=new QueryRunner();Connection connection = JDBCUtils.getConnection();String sql="select id,name,email,birth from customers where id=?";BeanHandler handler=new BeanHandler(Customer.class);Customer query = runner.query(connection, sql, handler, 21);System.out.println(query);JDBCUtils.closeResource(connection,null);}//BeanListHandler:是ResultSetHandler接口的实现类,用于封装表中的多条记录@Testpublic void testQ2() throws Exception {QueryRunner runner=new QueryRunner();Connection connection = JDBCUtils.getConnection();String sql="select id,name,email,birth from customers where id handler=new BeanListHandler(Customer.class);List query = runner.query(connection, sql, handler, 21);System.out.println(query);JDBCUtils.closeResource(connection,null);}//ScalarHandler:是ResultSetHandler接口的实现类,用于查询特殊值@Testpublic void testScalar() throws Exception {QueryRunner runner=new QueryRunner();Connection connection = JDBCUtils.getConnection();String sql="select count(*) from customers";ScalarHandler handler=new ScalarHandler();Object query = runner.query(connection, sql, handler);System.out.println(query);dbClose(connection,null,null);//JDBCUtils.closeResource(connection,null);}//使用dbutils.jar中提供的DbUtils工具类,实现资源关闭public void dbClose(Connection conn, Statement ps, ResultSet rs) {try {DbUtils.close(conn);} catch (SQLException sqlException) {sqlException.printStackTrace();}try {DbUtils.close(rs);} catch (SQLException sqlException) {sqlException.printStackTrace();}try {DbUtils.close(ps);} catch (SQLException sqlException) {sqlException.printStackTrace();}}
}

其中获取连接还是需要自己的工具类获取连接,因为你的数据库只有你知道是什么名字和密码

初学者,可能有些地方的总结或者代码有错误,谅解


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章