java中的数据存储技术
在Java中,数据库存取技术可分为如下几类:
JDBC直接访问数据库
JDO (Java Data Object )技术
第三方O/R工具,如Hibernate, Mybatis 等
JDBC是java访问数据库的基石,JDO、Hibernate、MyBatis等只是更好的封装了JDBC。jdbc识sun发布的一个java程序和数据库之间通信的规范(接口)
JDBC体系结构
JDBC接口(API)包括两个层次:
面向应用的API:Java API,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结果)。
面向数据库的API:Java Driver API,供开发商开发数据库驱动程序用。zhu
1、JDBC的常规操作
1). JDBC连接MySQL数据库
要先导入mysql-jdbc(驱动)jar包(创建一个lib目录,里面存放该jar包,记得要添加为类库。(个大数据库厂商去实现JDBC规范(实现类),这些实现类打成压缩包,就是所谓的jar包。mysql驱动的下载MySQL :: Download Connector/J
(注意:如果是Dynamic Web Project(动态的web项目)话,则是把驱动jar放到WebContent(有的开发工具叫 WebRoot)目录中的WEB-INF目录中的lib目录下即可
JDBC程序编写步骤:
1、注册驱动DriverManager
2、获取数据库连接
3、创建发送sql语句对象
4、发送sql语句,并获取返回结果
5、结果集解析
6、资源关闭
1. JDBC常规操作:
1). JDBC连接MySQL数据库
关于驱动:
/* 注册驱动: 依赖:驱动版本 8+ com.mysql.cj.jdbc.Driver 驱动版本 5+ com.mysql.jdbc.Driver */
加载驱动:加载 JDBC 驱动需调用 Class 类的静态方法 forName(),向其传递要加载的 JDBC 驱动的类名,Class.forName(“com.mysql.jdbc.Driver”);
注册驱动:DriverManager 类是驱动程序管理器类,负责管理驱动程序
//方案1 // DriverManager.registerDriver(new Driver()); //方案2:注册驱动 固定写法 mysql - mysql Driver || 不灵活,换成oracle 等别的数据库需要再改代码 // new Driver(); //方案3:反射 //字符串的Driver全限定词,可以引导外部的配置文件-》xx.properties -> oracle -> 配置文件修改 Class.forName("com.mysql.cj.jdbc.Driver"); /* 方案一问题:会注册两次驱动 1、 DriverManager.registerDriver();方法本身会注册一次 2、 Driver.static{ DriverManager.registerDriver()} 静态代码块也会注册一次 解决:只触发静态代码块 Driver 触发静态代码块:类加载时会触发 加载【class文件--》java虚拟机和class对象 连接【验证(检查文件类型)-》准备(静态变量默认值)-》解析(触发静态代码块) 初始化(静态属性赋真实值) 触发类加载: 1、new 2、调用类的静态方法 3、调用静态属性 4、接口 1.8 default默认实现 5、反射11 6、子类触发父类 (子类实例化会触发父类) 7、程序的入口main */
(如图如果DriverManager.registerDriver(new Driver());来注册驱动,通常不用显式调用 DriverManager 类的 registerDriver() 方法来注册驱动程序类的实例,则相当于DriverManager.registerDriver调用了两次自己,注册了两次驱动。所以采用方案三来注册驱动合适。
关于获取数据库连接
//方式1 Connection connection = DriverManager.getConnection("jdbc:mysql:///forget", "root", "1234567"); //方式2 Properties info = new Properties(); info.put("user","root"); info.put("password","1234567"); Connection connection1 = DriverManager.getConnection("jdbc:mysql:///forget", info); //方式3 DriverManager.getConnection("jdbc:mysql:///forget?user=root&password=1234567"); /* getConnection(1,2,3)是一个重载方法,下面对其参数形式作解释,上面是示例 核心属性: 1、数据库软件所在的主机的ip地址: localhost | 127.0.0.1 2、数据库软件所在的主机的端口号:3306(默认的端口号是这个) 3、连接的具体数据库: 4、连接的账号、密码 5、可选信息: 三个参数: String url:数据库软件所在信息,连接的具体库,以及其他可选信息 语法:jdbc;数据库管理软件名称【mysql,oracle]://ip地址|主机名:port端口号/数据库名?key=value&key=value 可选信息 具体: jdbc:mysql://127.0.0.1:3306/forget jdbc:mysql://localhost:3306/forget 本机省略写法:如果你的数据库软件安装到本机,可以进行一些省略 jdbc:mysql:///forget (forget是我创建的数据库名字) String user: String password: user,password可以用“属性名=属性值”方式告诉数据库 也可以调用 DriverManager 类的 getConnection() 方法建立到数据库的连接 两个参数: String url: properties info:储存账号和密码: properties 类似于Map 只不过key = value 都是字符串形式 key user :账号信息 key password :密码信息 一个参数::数据库ip,端口号,具体数据库 jdbc:数据库软件名://ip:port/forget?key=value&key=value&key=value jdbc:数据库软件名://ip:port/forget?key=value&key=value&key=v */
URL:JDBC URL 用于标识一个被注册的驱动程序,驱动程序管理器通过这个 URL 选择正确的驱动程序,从而建立到数据库的连接。
JDBC URL的标准由三部分组成,各部分间用冒号分隔。
jdbc:子协议:子名称
协议:JDBC URL中的协议总是jdbc
子协议:子协议用于标识一个数据库驱动程序子名称:一种标识数据库的方法。子名称可以依不同的子协议而变化,用子名称的目的是为了定位数据库,提供足够的信息。包含主机名(对应服务端的ip地址),端口号,数据库名
几种常用数据库的 JDBC URL MySQL的连接URL编写方式: jdbc:mysql://主机名称:mysql服务端口号/数据库名称?参数=值&参数=值 jdbc:mysql://localhost:3306/atguigu jdbc:mysql://localhost:3306/atguigu?useUnicode=true&characterEncoding=utf8(如果JDBC 程序与服务器端的字符集不一致,会导致乱码,那么可以通过参数指定服务器端的字符集) jdbc:mysql://localhost:3306/atguigu?user=root&password=123456 Oracle 9i的连接URL编写方式: jdbc:oracle:thin:@主机名称:oracle服务端口号:数据库名称 jdbc:oracle:thin:@localhost:1521:atguigu SQLServer的连接URL编写方式: jdbc:sqlserver://主机名称:sqlserver服务端口号:DatabaseName=数据库名称 jdbc:sqlserver://localhost:1433:DatabaseName=atguigu
综上只要注册驱动和获取连接只要使用如下就好
//1、注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //2、获取连接 Connection connection = DriverManager.getConnection("jdbc:mysql:///forget", "root", "1234567");
(其它讲法,
数据库连接的连接方式一:
public void connectiontest1(){ try { //1.提供java.sql.Driver接口实现类的对象 Driver driver = new com.mysql.cj.jdbc.Driver(); //2.提供url,指明具体操作的数据 String url ="jdbc:mysql://localhost:3306/jdbc"; //3.提供Properties的对象,指明用户名和密码 Properties info = new Properties(); info.setProperty("user","root"); info.setProperty("password","1234567"); //4.调用driver的connect(),获取连接 Connection connection = driver.connect(url,info); System.out.println(connection); } catch (SQLException e) { throw new RuntimeException(e); } }
如果打印结果输出为null,可能是url打错了。打印结果如下形式 Driver driver = new com.mysql.jdbc.Driver();可根据如下图理解, (上述代码中显式出现了第三方数据库的API)这里可以改进实例化Driver方式,获取Driver实现类时使用反射,不在代码中体现第三方数据库的API。体现了面向接口编程思想。换成如下
Class clazz = Class.forName("com.mysql.cj.jdbc.Driver");方式二:
使用DriverManager实现数据库的连接。体会获取连接必要的4个基本要素
public void connectiontest2() throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException { //1、实现Driver实现类对象 Class clazz = Class.forName("com.mysql.cj.jdbc.Driver"); Driver driver = (Driver) clazz.newInstance(); //2.数据库连接的3个基本信息: String url = "jdbc:mysql://localhost:3306/jdbc"; String user = "root"; String password = "1234567"; //3、注册驱动 DriverManager.registerDriver(driver); //4、获取连接 Connection connection = DriverManager.getConnection(url, user, password); System.out.println(connection); }
上面方法 DriverManager.registerDriver(driver);,前面分析时讲到不太适合。在这个基础上可以删掉,变成
String url = "jdbc:mysql://localhost:3306/jdbc"; String user = "root"; String password = "1234567"; Connection connection = DriverManager.getConnection(url, user, password); System.out.println(connection);
(可以省略是因为mysql的Driver实现类中声明:
static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } }
因为 Driver 接口的驱动程序类都包含了静态代码块,在这个静态代码块中,会调用 DriverManager.registerDriver() 方法来注册自身的一个实例。所以可以只是加载驱动,不必显式的注册驱动了。因为在DriverManager的源码中已经存在静态代码块,实现了驱动的注册。
方式三:使用配置文件的方式保存配置信息,在代码中加载配置文件
但是在mysql中可以省略,但是换成别的数据库可能就不行了,所以
Class.forName("com.mysql.cj.jdbc.Driver");还是不要省略了,创建properties文件存储需要用到的内容(记住=左右不要空格),该文件放在src目录下
public void connectiontest3() throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException, IOException { //1、加载配置文件 InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties"); Properties properties = new Properties(); properties.load(is);//加载这个文件 //2.读取配置信息 String user = properties.getProperty("user"); String password = properties.getProperty("password"); String url = properties.getProperty("url"); String driverClass = properties.getProperty("driverClass"); //3、加载驱动 Class.forName(driverClass); //4、获取连接 Connection connection = DriverManager.getConnection(url, user, password); System.out.println(connection); }
(①实现了代码和数据的分离,如果需要修改配置信息,直接在配置文件中修改,不需要深入代码 ②如果修改了配置信息,省去重新编译的过程。
补充:(在navicat中导入数据表成功却没有表,遇到这个问题,看到了这篇博客navicat导入sql文件成功但没有表_sql导入成功但是没有数据_czx鑫的博客-CSDN博客
使用PreparedStatement实现CRUD操作操作和访问数据库:数据库连接被用于向数据库服务器发送命令和 SQL 语句,并接受数据库服务器返回的结果。其实一个数据库连接就是一个Socket连接。
在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式: Statement:用于执行静态 SQL 语句并返回它所生成结果的对象。 PrepatedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。 CallableStatement:用于执行 SQL 存储过程
但是使用Statement操作数据表存在弊端:
Scanner scanner = new Scanner(System.in); System.out.println("请输入账号:"); String account = scanner.nextLine(); System.out.println("请输入密码:"); String password = scanner.nextLine(); Class.forName("com.mysql.cj.jdbc.Driver"); Connection connection=DriverManager.getConnection("jdbc:mysql:///forget?user=root&password=1234567"); //创建发送sql语句的statement对象 //statement 可以发送sql语句到数据库,并且获取返回结果 Statement statement = connection.createStatement(); //发送sql语句 String sql ="select * from user where account = '"+account+"'and password ='"+password+"';";
statement在后面查询结果集会使用到,如下
ResultSet resultSet = statement.executeQuery(sql);关于statement的使用
问题一:存在拼串操作,繁琐
问题二:存在SQL注入问题SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令。如下
一开始的sql语句:
"select * from user where user = '"+user+"'and password ='"+password+"';"
所以当如下输入时
user输入1' or
密码输入 =1 or '1' = '1 时,会改变sql语句的原意,变成
SELECT user, password FROM user_table WHERE user='1' OR 1 = ' AND password = ' OR '1' ='1')
意味着用户名为1或者密码为1或1=1(恒成立)。从而利用系统的 SQL 引擎完成恶意行为的做法。
对于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement(从Statement扩展而来) 取代 Statement 就可以了。
PreparedStatement的使用
可以通过调用 Connection 对象的 preparedStatement(String sql) 方法获取PreparedStatement 对象
//1、注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //2、获取连接 Connection connection = DriverManager.getConnection("jdbc:mysql:///forget", "root", "1234567"); //3、编写sql语句结果,动态值的部分使用?代替 String sql = "insert into user(account,password,nickname)values(?,?,?);"; //4、创建preparedStatement,并且传入sql语句结果 PreparedStatement preparedStatement = connection.prepareStatement(sql);
PreparedStatement 接口是 Statement 的子接口,它表示一条预编译过的 SQL 语句
PreparedStatement 对象所代表的 SQL 语句中的参数用问号(?)来表示,调用 PreparedStatement 对象的setXxx() 方法来设置这些参数. setXxx() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1开始),第二个是设置的 SQL 语句中的参数的值//5、占位符赋值 preparedStatement.setObject(1, "test"); preparedStatement.setObject(2, "test"); preparedStatement.setObject(3, "lulu");
相当于: insert into user(account,password,nickname)values("test","test","lulu");PreparedStatement的增删改
如下为添加数据操作
public void testInsert() { Connection connection = null; PreparedStatement preparedStatement = null; try { //1、获取配置信息 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"); //2、加载驱动 Class.forName(driverClass); //3、获取连接 connection = DriverManager.getConnection(url, user, password); //4、预编译sql语句,返回PreparedStatement的实例 String sql="insert into customers(name,email,birth) values(?,?,?)"; preparedStatement = connection.prepareStatement(sql); //5、填充占位符 preparedStatement.setObject(1,"周日"); preparedStatement.setObject(2,"[email protected]"); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); Date date = simpleDateFormat.parse("1909-02-01"); preparedStatement.setDate(3, new java.sql.Date(date.getTime())); //util下的date不能转换为sql下的date,所以(java.sql.Date)new (date.getTime())不行 //6、执行操作 preparedStatement.execute(); } catch (Exception e) { e.printStackTrace(); } finally { //7、资源的关闭 try { if(preparedStatement!=null){ preparedStatement.close(); } } catch (SQLException e) { e.printStackTrace(); } try { if(connection!=null){ connection.close(); } } catch (SQLException e) { e.printStackTrace(); } } }
遇到报错
https://www.cnblogs.com/We612/p/10849556.html,修改字符集即可,我是再navicat上利用图形化界面的操作实现的,但是可以俩姐下相关命令。
关于字符集,我当时没找到utf8,看到有utfmb3和utfmb4,我设置的是4。
mysql中没有utf8字符集_mysql之坑–UTF8字符集_帕腓尼基的博客-CSDN博客
接下来的删、改则通过对jdbc一些步骤封装成工具类后进行使用实现
public class JDBCUtils { public static Connection getConnection() throws Exception { //1、获取配置信息 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"); //2、加载驱动 Class.forName(driverClass); //3、获取连接 Connection connection = DriverManager.getConnection(url, user, password); return connection; } public static void closeResource(Connection con, PreparedStatement ps) { try { if (ps != null) { ps.close(); } } catch (SQLException e) { e.printStackTrace(); } try { if (con != null) { con.close(); } } catch (SQLException e) { e.printStackTrace(); } } }
如上对加载驱动,创建连接,PreparedStatemen语句、关闭资源等的封装
改
public void testUpdate() throws Exception { Connection connection = null; PreparedStatement ps = null; try { //1、通过封装的JDBCUtils工具类获取数据库连接 connection = JDBCUtils.getConnection(); //2、预编译sql语句,返回PreparedStatement的实例 String sql ="update customers set name = ? where id =?"; ps = connection.prepareStatement(sql); //3、填充占位符 ps.setObject(1,"tom"); ps.setObject(2,18); //4、执行 ps.execute(); } catch (Exception e) { e.printStackTrace(); } finally { //5、关闭资源 JDBCUtils.closeResource(connection,ps); } }
在工具类的基础上我们再改进一下,实现通用的增删改(就是把占位符赋值部分的功能封装
public static void CommonUse(String sql,Object...args){ Connection connection = null; PreparedStatement preparedStatement = null; try { connection = JDBCUtils.getConnection(); preparedStatement = connection.prepareStatement(sql); for (int i = 0; i < args.length; i++) { preparedStatement.setObject(i+1,args[i]); } preparedStatement.execute(); } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtils.closeResource(connection,preparedStatement); }
测试
@Test public void testDelete() throws Exception { //增 String add="insert into customers(name,email) value(?,?) "; CommonUse(add,"lucy","[email protected]"); //删 String dele="delete from customers where id = ? "; CommonUse(dele,20); //改 String update ="update customers set email=? where name=?"; CommonUse(update,"[email protected]","tom"); }
查询(之前增删改,使用的时execute()方法进行执行操作,因为需要返回结果集的缘故,后面则是其它方法)
a、查询需要调用PreparedStatement 的 executeQuery() 方法,查询结果是一个ResultSet 对象
b、ResultSet 对象以逻辑表格的形式封装了执行数据库操作的结果集,ResultSet 接口由数据库厂商提供实现
c、ResultSet 返回的实际上就是一张数据表。有一个指针指向数据表的第一条记录的前面。
d、ResultSet 对象维护了一个指向当前数据行的游标,初始的时候,游标在第一行之前,可以通过 ResultSet 对象的 next() 方法移动到下一行。调用 next()方法检测下一行是否有效。若有效,该方法返回 true,且指针下移。相当于Iterator对象的 hasNext() 和 next() 方法的结合体。
e、当指针指向一行时, 可以通过调用 getXxx(int index) 或 getXxx(int columnName) 获取每一列的值。
例如: getInt(1), getString("name")
注意:Java与数据库交互涉及到的相关Java API中的索引都从1开始。
ResultSet 接口的常用方法:
boolean next()
getString()
相关查询操作
(下图表示Java与SQL对应数据类型转换表)
一个数据表对应一个java类,表中的一条记录对应java类的一个对象,表中的一个字段对应java类的一个属性。我们创建一个Customer类来封装一条记录
Customer类中,成员变量如下(记得还要生成get和set方法,带参的构造方法)
public class Customer { private int id; private String name; private String email; private Date brith;
测试
public void CustomerForQuery() { Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { connection = JDBCUtils.getConnection(); String sql = "select * from customers"; preparedStatement = connection.prepareStatement(sql); resultSet = preparedStatement.executeQuery(); while (resultSet.next()) { Integer id = resultSet.getInt(1); String name = resultSet.getString(2); String eamil = resultSet.getString(3); Date brith = resultSet.getDate(4); Customer customer = new Customer(id, name, eamil, brith); System.out.println(customer); } } catch (Exception e) { throw new RuntimeException(e); } finally { try { if (resultSet != null) { resultSet.close(); } } catch (Exception e) { e.printStackTrace(); } try { if (preparedStatement != null) { preparedStatement.close(); } } catch (Exception e) { e.printStackTrace(); } try { if (connection != null) { connection.close(); } } catch (Exception e) { e.printStackTrace(); } } }
(但是这种是针对某一具体实体类型的数据封装
ResultSetMetaData
可用于获取关于 ResultSet 对象中列的类型和属性信息的对象
ResultSetMetaData meta = rs.getMetaData(); getColumnName(int column):获取指定列的名称 getColumnLabel(int column):获取指定列的别名 getColumnCount():返回当前 ResultSet 对象中的列数。 getColumnTypeName(int column):检索指定列的数据库特定的类型名称。 getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。 isNullable(int column):指示指定列中的值是否可以为 null。 isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的。
如下代码中对它的使用
原来是没有对应字段造成的(在表中字段名为birth,在实体类中为brith,没对应上),还有就是注意查询字段和属性名一一对应(查询的sql语句的编写),(造成如上报错的原因还可能是属性为私有时获取Field用的方法不是getDeclaredField。)
如下为查询结果:
Customer{id=1, name='张三', email='[email protected]', birth=2010-02-02}
a
总结
JDBC对数据库进行增、删、改、查
(补充:
关于反射
下图为从内存中看反射
Class对象是反射的根源。
在Object类中定义了以下的方法,此方法将被所有子类继承:
public final Class getClass()获取 Class 类的实例 四种方法
方式1:要求编译期间已知类型 (Class clazz = String.class;)
方式2:获取对象的运行时类型
前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象
Class clazz = "www.atguigu.com".getClass();
方式3:可以获取编译期间未知的类型
前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException
方式4:其他方式
前提:可以用系统类加载对象或自定义加载器对象加载指定路径下的类型实例:
ClassLoader cl = this.getClass().getClassLoader();Class clazz4 = cl.loadClass("类的全类名");
/* 关于java.lang.Class类的理解 1、类的加载过程 程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾) 接着我们使用java.exe命令对某个字节码文件进行解释运行,相当于将某个字节码文件加载到内存中。 此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例 2、换句话说,Class的实例就对应着一个运行时类 3、加载到内存中的运行时类,会缓存一定时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。 */ //获取Class的实例的方式 @Test public void test3() throws ClassNotFoundException { //方式一:调用运行时类的属性: .class Class clazz1 = Person.class; //Class
clazz1 = Person.class; System.out.println(clazz1); //class SE.Reflection.Person //方式二:调用运行时类的对象 Person p1 = new Person(); Class clazz2 = p1.getClass(); System.out.println(clazz2); //class SE.Reflection.Person //方式三:调用Class的静态方法: forName(String classPath) Class clazz3 = Class.forName("SE.Reflection.Person");//类的全类名,包含包名在内的完整路径 System.out.println(clazz3); //class SE.Reflection.Person System.out.println(clazz1 == clazz2);//true System.out.println(clazz1 == clazz3);//true //方式四:使用类的加载器:ClassLoader ClassLoader classLoader = ReflectionTest.class.getClassLoader(); Class clazz4 = classLoader.loadClass("SE.Reflection.Person"); System.out.println(clazz4); //class SE.Reflection.Person System.out.println(clazz1 == clazz4);//true } 类的加载器(以jdk8为例)
JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)。在程序中我们最常见的类加载器结构主要是如下情况:
(启动类加载器(引导类加载器,Bootstrap ClassLoader)它用来加载 Java 的核心库( JAVA_HOME/jre/lib/rt.jar 或 sun.boot.class.path 路径下的内容)。用于提供 JVM 自身需要的类。
• 并不继承自 java.lang.ClassLoader ,没有父加载器。扩展类加载器(Extension ClassLoader)由 sun.misc.Launcher$Ex tClassLoader 实现。
应用程序类加载器(系统类加载器,AppClassLoader)它负责加载环境变量 classpath 或系统属性 java.class.path 指定路径下的类库
• 应用程序中的类加载器默认是系统类加载器。
• 它是用户自定义类加载器的默认父加载器
• 通过 ClassLoader 的 getSystemClassLoader() 方法可以获取到该类加载器查看某个类的类加载器对象 (1)获取默认的系统类加载器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2 (2)查看某个类是哪个类加载器加载的 //如果是根加载器加载的类,则会得到null ClassLoader classloader1 = Class.forName("java.lang.Object").getClassLoader(); System.out.println(classloader1);//null (3)获取某个类加载器的父加载器 ClassLoader parentClassloader = systemClassLoader.getParent(); System.out.println(parentClassloader);//sun.misc.Launcher$ExtClassLoader@372f7a8d
关于类加载器的一个主要方法:getResourceAsStream(String str):获取类路径下的指定文件的输入流。
public void test2() throws IOException { Properties pros = new Properties(); //此时的文件默认在当前的module下 //读取配置文件方式一: // FileInputStream fis = new FileInputStream("jdbc1.properties"); // FileInputStream fis = new FileInputStream("src//jdbc2.properties"); // pros.load(fis); //读取配置文件方式二: //配置文件默认识别为:当前module的src下 // ClassLoader classLoader = ClassLoaderTest.class.getClassLoader(); // InputStream is = classLoader.getResourceAsStream("jdbc2.properties"); // pros.load(is); //获取配置文件中的信息 String user = pros.getProperty("user"); String password = pros.getProperty("password"); System.out.println("user="+user+","+"password="+password); //user=小小,password=123456 }
反射的应用
应用 1 :创建运行时类的对象
方式1:直接调用Class对象的newInstance()方法
newInstance():调用此方法,创建对应的运行时类的对象 内部调用了运行时类的空参构造器 用此方法正常创建运行时类的对象,要求满足: 1、运行时类必须提供空参构造器 2、空参的构造器的访问权限得够。通常,设置为public // Class clazz = Person.class; // Person obj = (Person) clazz.newInstance(); Class
clazz = Person.class; //这样写后面就不用强转 Person obj = clazz.newInstance(); //Person{name='null', age=0} 方式2:通过获取构造器对象来进行实例化
1)通过Class类的getDeclaredConstructor(Class … parameterTypes)取得本类的指定形参类型的构造器 2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。 3)通过Constructor实例化对象。 如果构造器的权限修饰符修饰的范围不可见,也可以调用setAccessible(true) Class
clazz = Person.class; Constructor>[] constructors = clazz.getDeclaredConstructors(); constructors[2].setAccessible(true); Object obj = constructors[2].newInstance("today"); System.out.println(obj);//Person{name='today', age=0} //如果构造器有多个,我们通常是根据形参【类型】列表来获取指定的一个构造器的 Constructor> constructors1 = clazz.getDeclaredConstructor(String.class); constructors1.setAccessible(true); Object obj1 = constructors1.newInstance("today1"); System.out.println(obj1);//Person{name='today1', age=0} 应用 2 :获取运行时类的完整结构
可以获取:包、修饰符、类型名、父类(包括泛型父类)、父接口(包括泛型父接口)、成员(属性、构造器、方法)、注解(类上的、方法上的、属性上的)。
//1.实现的全部接口 public Class>[] getInterfaces() //确定此对象所表示的类或接口实现的接口。 //2.所继承的父类 public Class Super T> getSuperclass() //返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的 Class。 //3.全部的构造器 public Constructor
[] getConstructors() //返回此 Class 对象所表示的类的所有public构造方法。 public Constructor [] getDeclaredConstructors() //返回此 Class 对象表示的类声明的所有构造方法。 //Constructor类中: //取得修饰符: public int getModifiers(); //取得方法名称: public String getName(); //取得参数的类型: public Class>[] getParameterTypes(); //4.全部的方法 public Method[] getDeclaredMethods() //返回此Class对象所表示的类或接口的全部方法 public Method[] getMethods() //返回此Class对象所表示的类或接口的public的方法 //Method类中: public Class> getReturnType() //取得全部的返回值 public Class>[] getParameterTypes() //取得全部的参数 public int getModifiers() //取得修饰符 public Class>[] getExceptionTypes() //取得异常信息 //5.全部的Field public Field[] getFields() //返回此Class对象所表示的类或接口的public的Field。 public Field[] getDeclaredFields() //返回此Class对象所表示的类或接口的全部Field。 //Field方法中: public int getModifiers() //以整数形式返回此Field的修饰符 public Class> getType() //得到Field的属性类型 public String getName() //返回Field的名称。 //6. Annotation相关 get Annotation(Class annotationClass) getDeclaredAnnotations() //7.泛型相关 //获取父类泛型类型: Type getGenericSuperclass() //泛型类型: ParameterizedType //获取实际的泛型类型参数数组: getActualTypeArguments() //8.类所在的包 Package getPackage() public class UseTest{ /* 获取当前运行时类的属性结构 */ @Test public void test(){ Class clazz = Person1.class; //获取属性结构 //getFields():获取当前运行时类及其父类中声明为public访问权限的属性 Field[] fields = clazz.getFields(); for (Field f : fields) { System.out.println(f); //public int SE.Reflection.Person1.id //public double SE.Reflection.Creature.weight } System.out.println("----------------"); //getDeclaredFields():获取当前运行时类中声明的所有属性。(不包含父类中声明的属性 Field[] declaredFields = clazz.getDeclaredFields(); for (Field f : declaredFields) { System.out.println(f); //private java.lang.String SE.Reflection.Person1.name //int SE.Reflection.Person1.age //public int SE.Reflection.Person1.id } } //权限修饰符 数据类型 变量名 @Test public void test2(){ Class clazz = Person1.class; Field[] declaredFields = clazz.getDeclaredFields(); for (Field f :declaredFields) { //1、获取权限修饰符 int modifiers = f.getModifiers(); System.out.print(Modifier.toString(modifiers)+"\t"); //2、数据类型 Class type = f.getType(); System.out.print(type.getName()+"\t"); //class java.lang.String type //java.lang.String type.getName() //3、变量名 String fName = f.getName(); System.out.println(fName); System.out.println(); } } /* 获取运行时类的方法结构 */ @Test public void test3(){ Class clazz= Person1.class; //getMethods():获取当前运行时类及其父类中声明为public权限的方法 Method[] methods = clazz.getMethods(); for (Method m : methods) { System.out.println(m); //public int SE.Reflection.Person1.compareTo(java.lang.Object) //public int SE.Reflection.Person1.compareTo(java.lang.String) //public void SE.Reflection.Person1.info() //public java.lang.String SE.Reflection.Person1.display(java.lang.String) //public void SE.Reflection.Creature.eat() //public final void java.lang.Object.wait() throws java.lang.InterruptedException //public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException //public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException //public boolean java.lang.Object.equals(java.lang.Object) //public java.lang.String java.lang.Object.toString() //public native int java.lang.Object.hashCode() //public final native java.lang.Class java.lang.Object.getClass() //public final native void java.lang.Object.notify() //public final native void java.lang.Object.notifyAll() } System.out.println("----------------"); //getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法 Method[] declaredMethods = clazz.getDeclaredMethods(); for (Method m : declaredMethods) { System.out.println(m); //public int SE.Reflection.Person1.compareTo(java.lang.Object) //public int SE.Reflection.Person1.compareTo(java.lang.String) //public void SE.Reflection.Person1.info() //public java.lang.String SE.Reflection.Person1.display(java.lang.String) //private java.lang.String SE.Reflection.Person1.show(java.lang.String) } } /* @Xxx 权限修饰符 返回值类型 方法名(参数类型1,形参名1...) throws XxxException{} */ @Test public void test4(){ Class clazz = Person1.class; //1、获取方法声明的注解 Method[] declaredMethods = clazz.getDeclaredMethods(); for (Method method : declaredMethods) { Annotation[] annotations = method.getAnnotations(); for (Annotation a :annotations) { System.out.print(a); //@SE.Reflection.MyAnnotation(value=hello) //@SE.Reflection.MyAnnotation(value=today is Monday) } //2、每个方法的权限修饰符 System.out.print(Modifier.toString(method.getModifiers())+"\t"); //3、返回值类型 System.out.print(method.getReturnType().getName()+"\t"); //4、方法名 System.out.print(method.getName()); System.out.print("("); //5、形参列表 Class[] parameterTypes = method.getParameterTypes(); if(!(parameterTypes==null&¶meterTypes.length==0)){ for (int i=0;i
0){ for (int i = 0; i < exceptionTypes.length; i++) { System.out.print("throws "); if(i==exceptionTypes.length-1){ System.out.print(exceptionTypes[i].getName()); break; } System.out.print(exceptionTypes[i].getName()+","); } } System.out.println(); // public volatile int compareTo(java.lang.Objectargs_0) //public int compareTo(java.lang.Stringargs_0) //public void info()throws java.lang.ExceptionInInitializerError,throws java.lang.NumberFormatException //@SE.Reflection.MyAnnotation(value=today is Monday)private java.lang.String show(java.lang.Stringargs_0) //@SE.Reflection.MyAnnotation(value=hello)public java.lang.String display(java.lang.Stringargs_0)throws java.lang.NullPointerException } } /* 获取构造器结构 */ @Test public void test5(){ Class clazz = Person1.class; //getConstructors():当前运行时类中声明为public的构造器 Constructor[] constructors = clazz.getConstructors(); for (Constructor c : constructors) { System.out.println(c); //public SE.Reflection.Person1() } System.out.println(); //getDeclaredConstructors():获取当前运行时类声明的所有的构造器 Constructor[] declaredConstructors = clazz.getDeclaredConstructors(); for (Constructor c:declaredConstructors) { System.out.println(c); //SE.Reflection.Person1(java.lang.String,int) //private SE.Reflection.Person1(java.lang.String) //public SE.Reflection.Person1() } } /* 获取运行时类的带泛型的父类 */ @Test public void test6(){ Class clazz = Person1.class; Type genericSuperclass = clazz.getGenericSuperclass(); System.out.println(genericSuperclass); //SE.Reflection.Creature //获取运行时类的带泛型的父类的泛型 ParameterizedType paramType = (ParameterizedType) genericSuperclass; //1获取泛型类型 Type[] actualTypeArguments = paramType.getActualTypeArguments(); for (Type t : actualTypeArguments) { //System.out.println(t.getTypeName()); // java.lang.String System.out.println(((Class) t).getName()); //java.lang.String } } /* 获取运行时类实现的接口 */ @Test public void test7(){ Class clazz = Person1.class; Class[] interfaces = clazz.getInterfaces(); for (Class c : interfaces) { System.out.println(c); //interface java.lang.Comparable //interface SE.Reflection.MyInterface } System.out.println(); //获取运行时类的父类实现的接口 Class[] interfaces1 = clazz.getSuperclass().getInterfaces(); for (Class c : interfaces1) { System.out.println(c); //interface java.io.Serializable } } /* 获取运行时类所在的包 */ @Test public void test8(){ Class clazz = Person1.class; Package pack = clazz.getPackage(); System.out.println(pack); //package SE.Reflection } /* 获取运行时类声明的注解 */ @Test public void test9(){ Class clazz = Person1.class; Annotation[] annotations = clazz.getAnnotations(); for (Annotation a : annotations) { System.out.println(a); //@SE.Reflection.MyAnnotation(value=hi) } } } 调用运行时类中指定的结构:属性、方法、构造器
public class UseTest1 { /* 如何操作运行时类中指定的属性---掌握 */ @Test public void testField() throws NoSuchFieldException, InstantiationException, IllegalAccessException { Class clazz = Person1.class; //创建运行时类的对象 Person1 p = (Person1) clazz.newInstance(); //获取指定属性:要求运行时类中属性声明为public //通常不采用 Field id = clazz.getField("id"); /* 设置当前属性的值 set():参数1:指明设置哪个对象的属性 参数2:将此属性值设置为多少 */ id.set(p, 11001); /* 获取当前属性的值 get():参数1:获取哪个对象的当前属性值 */ int pid = (int) id.get(p); System.out.println(pid); System.out.println("------------------"); // 1\getDeclaredField(String fieldname):获取当前运行时类中指定变量名的属性 Field name = clazz.getDeclaredField("name"); //2\保证当前属性是可访问的 name.setAccessible(true); //3\获取、设置指定对象的此属性值 name.set(p, "forget"); System.out.println(name.get(p)); } /* 如何操作运行时类中指定的方法 */ @Test public void testMethod() throws Exception { Class clazz = Person1.class; //创建运行时类的对象 Person1 p = (Person1) clazz.newInstance(); /* 1、获取指定的某个方法 getDeclaredMethod():参数1:指明获取方法的名称 参数2:指明获取的方法的形参列表 */ Method show = clazz.getDeclaredMethod("show", String.class); //保证当前方法是可访问的 show.setAccessible(true); /* 2、调用方法invoke():参数1:方法的调用者 参数2:给方法形参赋值的实参 invoke()的返回值即为对应类中调用的方法的返回值 */ Object returnValue = show.invoke(p, "china"); //我的国籍是:china //String nation = show.invoke(p, "china"); System.out.println(returnValue);//china System.out.println("-------如何调用静态方法------"); // private static void showDesc() Method showDesc = clazz.getDeclaredMethod("showDesc"); showDesc.setAccessible(true); //如果调用的运行时类中的方法没有返回值,则此invoke()返回null Object returnValue1 = showDesc.invoke(null); //Object returnValue1 = showDesc.invoke(Person1.class); System.out.println(returnValue1);//null } /* 如何调用运行时类中指定的构造器 */ @Test public void testConstructor() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Class clazz = Person1.class; //private Person1(String name) /* 1\获取指定的构造器 getDeclaredConstructor():参数:指明构造器的参数列表 */ Constructor constructor = clazz.getDeclaredConstructor(String.class); //2\保证此构造器是可访问的 constructor.setAccessible(true); //3\调用此构造器创建运行时类的对象 Person1 per = (Person1) constructor.newInstance("Tom"); System.out.println(per); //Person1{name='Tom', age=0, id=0} } }
补充:
发现在navicat直接按f6可以进行输入命令行操作,如下
添加操作时获取自增列主键值
/** * user插入一条数据并且获取数据库自增长的主键 * 1、创建preparedStatement时,告知带回数据库自增长的主键 * PreparedStatement preparedStatement = connection.prepareStatement(sql,PreparedStatement.RETURN_GENERATED_KEYS); * 2、获取装主键值的结果集对象,一行一列,获取对应的数据即可 * ResultSet resultSet = preparedStatement.getGeneratedKeys(); * resultSet.next(); * int id = resultSet.getInt(1); */ @Test public void resultPrinaryKey() throws SQLException, ClassNotFoundException { Class.forName("com.mysql.cj.jdbc.Driver"); Connection connection = DriverManager.getConnection("jdbc:mysql:///web", "root", "1234567"); String sql = "insert into tb_student(sname,gender,age) values(?,?,?);"; PreparedStatement preparedStatement = connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS); preparedStatement.setObject(1, "wang"); preparedStatement.setObject(2, "女"); preparedStatement.setObject(3, 21); int update = preparedStatement.executeUpdate(); if (update > 0) { System.out.println("插入成功!"); ResultSet resultSet = preparedStatement.getGeneratedKeys(); resultSet.next(); int id = resultSet.getInt(1); System.out.println("id=" + id); } else { System.out.println("插入失败!"); } preparedStatement.close(); connection.close(); }
批量执行SQL语句
JDBC的批量处理语句包括下面三个方法:
addBatch(String):添加需要批量处理的SQL语句或是参数;
executeBatch():执行批量处理语句;
clearBatch(): 清空缓存的数据
通常我们会遇到两种批量执行SQL语句的情况:
多条SQL语句的批量处理;
一个SQL语句的批量传参;(mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持。
* ?rewriteBatchedStatements=true 写在配置文件的url后面)public void testInsert() throws SQLException, ClassNotFoundException { /** * 使用批量插入的方式插入10000条数据 * 1、路径后添加rewriteBatchedStatements=true,允许批量插入 * 2、insert into values 必须写,且语句不能添加;结束 * 3、不是执行语句,是批量添加,addBatch() * 4、遍历添加 完毕以后,统一批量执行executeBatch() */ Class.forName("com.mysql.cj.jdbc.Driver"); Connection connection = DriverManager.getConnection("jdbc:mysql:///web?rewriteBatchedStatements=true", "root", "1234567"); String sql = "insert into user(account,password,nickname) values(?,?,?)"; PreparedStatement preparedStatement = connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS); long start = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { preparedStatement.setObject(1, "w"+i); preparedStatement.setObject(2, "1s"+i); preparedStatement.setObject(3, "xs"+i); preparedStatement.addBatch();//不执行,接到values后面 } preparedStatement.executeBatch();//执行批量操作 long end = System.currentTimeMillis(); System.out.println("执行10000次数据插入消耗时间:"+(end-start)); preparedStatement.close(); connection.close(); }
如果做出如下改进,即执行一部分一部分执行,则会提高速度。
for (int i = 0; i < 10000; i++) { preparedStatement.setObject(1, "w"+i); preparedStatement.setObject(2, "女"+i); preparedStatement.setObject(3, i+10); //1.“攒”sql preparedStatement.addBatch(); if(i % 500 == 0){ //2.执行 preparedStatement.executeBatch(); //3.清空 preparedStatement.clearBatch(); }
数据源连接池
工作原理
多种开源的数据库连接池
(特别注意:
数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。
当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但conn.close()并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。)Druid(德鲁伊)数据库连接池
Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,可以说是目前最好的连接池之一。硬编码方式:如下把参数信息用代码表示(不易修改,灵活性差,不推荐)
public class DruidUsePart { /** * 直接使用代码设置连接池连接参数方式 * 1、创建一个druid连接对象 * 2、设置连接池参数【必须|非必须】 * 3、获取连接【通用方法,所有连接池都一样】 * 4、回收连接【通用方法 */ public void testHard() throws SQLException { //连接池对象 DruidDataSource druidDataSource = new DruidDataSource(); //设置参数 //必须 连接数据库驱动类的全限定符【注册驱动】 | url | user | password druidDataSource.setUrl("jdbc:mysql:///jdbc"); druidDataSource.setUsername("root"); druidDataSource.setPassword("1234567"); druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); //非必须 初始化连接数量,最大的连接数量 druidDataSource.setInitialSize(4);//初始化连接数量 druidDataSource.setMaxActive(19);//最大数量 //获取连接 Connection connection = druidDataSource.getConnection(); //数据库curd //回收连接 connection.close();//连接池提供的连接,close,就是回收连接 } }
软编码方式:如下将配置信息写入配置文件,通过读取配置文件获取参数
properties文件要自己创建,里面放入上面代码所表示的信息
getResourceAsStream("druid.properties");注意这里的写法,如果是在src下则直接写文件名
/** * 通过读取外部配置文件的方法,实例化druid连接池的对象 */ @Test public void testSoft() throws Exception { //1、读取外部的配置文件 Properties properties = new Properties(); //src下的文件,可以使用类加载器提供的方法 InputStream resourceAsStream = DruidUsePart.class.getClassLoader().getResourceAsStream("druid.properties"); properties.load(resourceAsStream); //2、使用连接池的工具类的工厂模式,创建连接池 DataSource dataSource = DruidDataSourceFactory.createDataSource(properties); Connection connection = dataSource.getConnection(); //数据库curd操作在创建连接后的这里实现即可 connection.close(); }
(补充:配置文件里的相关信息
把创建连接池封装到一个类中。
public class DruidTest { private static DataSource dataSource=null; static { try { //1.加载配置文件 Properties properties = new Properties(); InputStream resourceAsStream = DruidTest.class.getClassLoader().getResourceAsStream("jdbc.properties"); properties.load(resourceAsStream); //2.获取DataSource dataSource = DruidDataSourceFactory.createDataSource(properties); } catch (Exception e) { e.printStackTrace(); } } //获取连接 public static Connection getConnection() throws Exception { return dataSource.getConnection(); } //关闭资源 public static void close(Connection connection, PreparedStatement preparedStatement){ close(connection,preparedStatement,null); } public static void close(Connection connection, PreparedStatement preparedStatement, ResultSet resultSet){ try { if(resultSet!=null){ resultSet.close(); } } catch (Exception e) { e.printStackTrace(); } try { if(preparedStatement!=null){ preparedStatement.close(); } } catch (Exception e) { e.printStackTrace(); } try { if(connection!=null){ connection.close(); } } catch (Exception e) { e.printStackTrace(); } } }
Apache-DBUtils实现CRUD操作
commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。
a
Dbutils三个核心功能
QueryRunner中提供对sql语句操作的API.
ResultSetHandler接口,用于定义select操作后,怎样封装结果集.
DbUtils类是一个工具类,定义了关闭资源与事务处理的方法
QueryRunner核心方法
QueryRunner(DataSource ds) ;传入参数为连接池
update(String sql, Object… params) ,执行insert update delete操作(增删改)
query(String sql, ResultSetHandler rsh, Object… params) ,执行 select操作
ResultSetHandler结果集处理
例如测试添加
public void testInsert() { Connection connection=null; try { QueryRunner queryRunner = new QueryRunner(); connection= DruidTest.getConnection(); String sql = "insert into tb_student(sid,sname,gender,age)values(?,?,?,?)"; int update = queryRunner.update(connection, sql, 2, "forget", "女", 19); System.out.println(update); } catch (Exception e) { throw new RuntimeException(e); }finally { DruidTest.close(connection,null); } }
删除(如下就是sql语句不同和改变参数即可)
public void testDetele() { Connection connection=null; try { QueryRunner queryRunner = new QueryRunner(); connection= DruidTest.getConnection(); String sql = "delete from tb_student where sid = ?"; int update = queryRunner.update(connection, sql, 2); System.out.println(update); } catch (Exception e) { throw new RuntimeException(e); }finally { DruidTest.close(connection,null); } } }
使用ResultSetHandler结果集处理
ResultSetHandler接口及实现类
该接口用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。
ResultSetHandler 接口提供了一个单独的方法:Object handle (java.sql.ResultSet .rs)。
接口的主要实现类:
ArrayHandler:把结果集中的第一行数据转成对象数组。 ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。 BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。 BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。 ColumnListHandler:将结果集中某一列的数据存放到List中。 KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map 里,其key为指定的key。 MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。 MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List ScalarHandler:查询单个值对象
使用ResultSetHandler的实现类:BeanHandler:查询一条记录
public void testSelect1() { Connection connection=null; try { QueryRunner queryRunner = new QueryRunner(); connection= DruidTest.getConnection(); String sql = "select * from tb_student where sid = ?"; BeanHandler
handler = new BeanHandler<>(Student.class); Student query = queryRunner.query(connection, sql, handler, 1); System.out.println(query); } catch (Exception e) { throw new RuntimeException(e); }finally { DruidTest.close(connection,null); } } 使用ResultSetHandler的实现类:BeanListHandler:查询多条记录构成的集合
public void testSelect2() { Connection connection=null; try { QueryRunner queryRunner = new QueryRunner(); connection= DruidTest.getConnection(); String sql = "select * from tb_student"; BeanListHandler
handler = new BeanListHandler<>(Student.class); List query = queryRunner.query(connection, sql, handler); System.out.println(query); } catch (Exception e) { throw new RuntimeException(e); }finally { DruidTest.close(connection,null); } } 使用ResultSetHandler的实现类:MapHandler:查询第一行数据
使用ResultSetHandler的实现类:MapListHandler:查询所有数据
使用ResultSetHandler的实现类:ScalarHandler:查询单个值的对象
如何查询类似于最大的,最小的,平均的,总和,个数相关的数据,
* 使用ScalarHandler自定义ResultSetHandler的实现类
public void testSelect6() { Connection connection=null; try { QueryRunner queryRunner = new QueryRunner(); connection= DruidTest.getConnection(); String sql = "select * from tb_student"; ResultSetHandler
> handler = new ResultSetHandler
>() { @Override public List
handle(ResultSet resultSet) throws SQLException { List list = new ArrayList<>(); System.out.println("自定义"); while(resultSet.next()){ int sid = resultSet.getInt(1); String sname = resultSet.getString(2); String gender = resultSet.getString(3); int age = resultSet.getInt(4); list.add(new Student(sid,sname,gender,age)); } return list; } }; List query = queryRunner.query(connection, sql, handler); query.forEach(System.out::println); } catch (Exception e) { throw new RuntimeException(e); }finally { DruidTest.close(connection,null); } } ( ResultSetHandler
> handler = new ResultSetHandler
>() {这里的泛型决定了下面重写方法的返回值,因为在方法中老是不能返回List集合,好像是因为一开始在泛型这里写的是Student
数据库事务
事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。
事务处理(事务操作):保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提(commit),那么这些修改就永久地保存下来;
要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态。
为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。JDBC事务处理 a、数据一旦提交,就不可回滚。 b、数据什么时候意味着提交? 当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成功,就会 向数据库自动提交,而不能回滚。 关闭数据库连接,数据就会自动的提交。如果多个操作,每个操作使用的是自己单独的连接,则无法保证 事务。即同一个事务的多个操作必须在同一个连接下。 c、JDBC程序中为了让多个 SQL 语句作为一个事务执行: 调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务 在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务 在出现异常时,调用 rollback(); 方法回滚事务
创建表
对字段进行设置让其值不能为负,才不会得到如下结果(
public class BankDao { /** * 加钱的数据库操作方法(jdbc) * account 加钱的账号 * money 加钱的金额 * */ public void add(String account,int money,Connection connection) throws ClassNotFoundException, SQLException { String sql = "update bank set money = money+? where account =?; "; PreparedStatement preparedStatement = connection.prepareStatement(sql); preparedStatement.setObject(1,money); preparedStatement.setObject(2,account); preparedStatement.executeUpdate(); System.out.println("加钱成功!"); } /** * 减钱的数据库操作方法(jdbc) * account 减钱的账号 * money 减钱的金额 * */ public void sub(String account,int money,Connection connection) throws ClassNotFoundException, SQLException { String sql = "update bank set money = money-? where account = ?; "; PreparedStatement preparedStatement = connection.prepareStatement(sql); preparedStatement.setObject(1,money); preparedStatement.setObject(2,account); preparedStatement.executeUpdate(); System.out.println("减钱成功!"); } }
public class BankService { @Test public void start() throws SQLException, ClassNotFoundException { transfer("小熊", "大大", 500); } /* 事务添加是在业务方法中: 利用try catch代码块,开启事务和提交事务,事务回滚 将connection传入dao层即可,dao只负责使用,不要close() */ public void transfer(String addAccount, String subAccount, int money) throws SQLException, ClassNotFoundException { BankDao bankDao = new BankDao(); Class.forName("com.mysql.cj.jdbc.Driver"); Connection connection = DriverManager.getConnection("jdbc:mysql:///jdbc", "root", "1234567"); try { //开启事务 connection.setAutoCommit(false); //执行数据库动作 bankDao.add(addAccount, money, connection); System.out.println("----------------------"); bankDao.sub(subAccount, money, connection); //事务提交 connection.commit(); } catch (Exception e) { // 5.若有异常,则回滚事务 try { connection.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } e.printStackTrace(); } finally { try { //6.恢复每次DML操作的自动提交功能 connection.setAutoCommit(true); } catch (SQLException e) { e.printStackTrace(); } //7.关闭连接 connection.close(); } } }
执行后得到
可以看到数据库中二者的钱数没有变化,可知进行了回滚
DAO及相关实现类
DAO:Data Access Object访问数据信息的类和接口,包括了对数据的CRUD(Create、Retrival、Update、
Delete),而不包含任何业务相关的信息。有时也称作:BaseDAO
作用:为了实现功能的模块化,更有利于代码的维护和升级。