Connection:此接口具有接触数据库的所有方法,该连接对象通信的上下文。
Statement:使用创建于这个接口的对象将数据提交到数据库
ResultSet:这个对象从数据库中获取得到的结果
SQLException:这个类处理数据库执行过程中的任何错误
import java.sql.*; public class JDBCDemo { public static void main(String[] args) { //加载数据库的驱动 mysql: com.mysql.jdbc.Driver try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } /** * 获取数据库的连接 DriverManager * URL格式: * jdbc:数据库子协议(mysql/oracle/sql sever) ://ip:port/database_name * 数据库的连接URL: jdbc:mysql://localhost:3306/exercise exercise 是库名 3306数据库端口号 * * 用户名:root * 密码:123456 */ //获取数据库的连接 DriverManager try { /** * getConnection源码 * * @param url a database url of the form * * @return a connection to the URL * 返回一个URL * 所以必须捕获异常,防止url为空 * * @exception SQLException if a database access error occurs or the url is * * {@code null} * * @throws SQLTimeoutException when the driver has determined that the * * timeout value specified by the {@code setLoginTimeout} method * * has been exceeded and has at least tried to cancel the * * current database connection attempt */ Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/exercise", "root", "123456"); //获取Statement的实例:通过connect实例获取 /** * createStatement * Creates a <code>Statement</code> object for sending SQL statements to the database. * 把sql语句给数据库 */ Statement statement = connection.createStatement(); /** * 执行SQL:1.创建基本的statement对象 * Statement statement = connection.createStatement(); */ //执行SQL String sql = "select * from student"; //将SQL提交给MySQL数据库 /** * executeQuery * * Executes the given SQL statement, which returns a single <code>ResultSet</code> object. * 执行sql语句 */ ResultSet resultSet = statement.executeQuery(sql); //对结果集的处理 System.out.println("SID:Sname:Ssex:Sage"); while (resultSet.next()) { //boolean next() 判断返回结果集中是否还有数据 //指定属性名 String sid = resultSet.getString("SID"); String sname = resultSet.getString("Sname"); String ssex = resultSet.getString("Ssex"); String sage = resultSet.getString("Sage"); System.out.println(sid+":"+sname+":"+ssex+":"+sage); } //关闭资源 statement.close(); connection.close(); } catch (SQLException e) { e.printStackTrace(); } } }
Connection: 1.获取执行sql的对象 2.管理事务:开启setAutoCommit(true)、提交(commit)、回滚(rollback)
int executeUpdate:返回的是影响的行数(可以通过返回的行数判断是否执行成功),执行表相关的方法(insert 、update、 delete ) 执行库相关的方法(create、alter 、drop)
ResultSet executeQuery:返回的是结果集,执行select语句
ResultSet:next判断是否还有下一行 getXxx:获取数据:getString、getInt
insert语句的练习:
注意要有释放资源的语句:
updata和delete的语句练习:
getXxx的参数问题:
查询的各个方法的应用:
练习:登陆,判断是否登陆成功
账号密码对应的数据表为:
package JdbcExercise; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; //2020/1/30 public class JDBCExer { public static void main(String[] args) { boolean flag=login("hui", "123"); if(flag){ System.out.println("登陆成功!!"); }else { System.out.println("登陆失败!"); } } public static boolean login(String username, String password){ if (username == null || password == null) { return false; } Connection connection = null; Statement statement=null; try { connection = JdbcUtils.getConnection(); String sql = "select * from user where username= '"+username+"' and password='"+password+"' "; //String sql1="select * from user"; statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(sql); return resultSet.next(); } catch (SQLException e) { e.printStackTrace(); }finally { JdbcUtils.close(statement,connection); } return false; } }
但是,如果将密码写成这样,也会登陆成功,即Sql注入问题
注:如果单引号没有拼接正确会报这样的异常:
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''' at line 1
上述问题采用预编译之后:
JDBC中SQL注入问题
sql注入性问题:通过用户输入的参数拼接SQL时,通过改变SQL语义来访问数据库获取结果的方式称之为SQL注入攻击
SQL注入问题如何解决:采用预编译机制的PreparedStatement,在SQL上将参数通过占位符'?'来代替参数,参数另外通过setString形式将参数设置PreparedStatement对象会分别将参数和SQL交给MySQL 服务器,执行之前会进行预编译,在预编译阶段会检测SQL是否正确,正确则将编译结果直接提交给SQL服务器执行,编译失败则直接返回,不在执行PreparedStatement主要的优势如下:
1、防止SQL注入
2、使用预编译机制,执行效率是高于Statement
3、sql语句中参数值是通过?形式来替代参数,在使用PreparedStatement方法set?对应的值,相对于SQL直接拼接更加优雅
1、语法不同:PreparedStatement可以使用预编译的SQL,
Statement使用静态SQL
2、效率不同:PreparedStatement执行效率高于Statement
3、安全性不同:PreparedStatement可以防止SQL注入 Statement不能防止SQL注入
注:主要区别 Statement对象: 创建语句参数不需要sql语句createStatement() 查询需要sql语句 executeOuery(sql);
2.预编译:
注:executeQuery() 查询函数,用于select语句,返回结果集
import java.sql.*; public class JDBCDemo { public static void main(String[] args) { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } try { Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/exercise", "root", "123456"); String sql1="select * from student where Ssex=?"; PreparedStatement statement1=connection.prepareStatement(sql1); //分别传到服务器 statement1.setString(1,"n"); //查找Sex=“n“的多行信息,1的意思是第一个参数因为只有一个Ssex属性 ResultSet resultSet = statement1.executeQuery(); //对结果集的处理 System.out.println("SID:Sname:Ssex:Sage"); while (resultSet.next()) { //boolean next() 判断返回结果集中是否还有数据 //指定属性名 String sid = resultSet.getString("SID"); String sname = resultSet.getString("Sname"); String ssex = resultSet.getString("Ssex"); String sage = resultSet.getString("Sage"); System.out.println(sid+":"+sname+":"+ssex+":"+sage); } //关闭资源 statement1.close(); connection.close(); } catch (SQLException e) { e.printStackTrace(); } } }
3.插入的预编译
注:插入语句后,返回的是影响的行数,所以返回int类型
int resultSet=statement1.executeUpdate(); 在preStatement() 函数中已经执行过了,所以在executeUpdate()中不用再加入sql;
加了之后会出现这样的错误(改了半天sql语句,结果...)
import java.sql.*; public class PreStaExercise { public static void main(String[] args) { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } try { Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/exercise", "root", "123456"); String sql="insert into student(Sid,Sname,Sage,Ssex) values (?,?,?,?) "; PreparedStatement statement1=con.prepareStatement(sql); statement1.setInt(1,7); statement1.setString(2,"hui"); statement1.setInt(3,22); statement1.setString(4,"w");
int resultSet=statement1.executeUpdate(); System.out.println(resultSet);
statement1.close(); con.close(); } catch (SQLException e) { e.printStackTrace(); } } }
添加后的student表:
因为试了很多遍,而且因为Sid作为主键不能重复。所以加入两行数据时,不能有相同的Sid,否则会报如下错。
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '5' for key 'PRIMARY'
练习1:定义一个方法,查询student表的数据将其封装为对象,然后装载集合,返回。
(将数据库查到的数据定义成一个类,放在一个List集合,此时的类类相当于javaBean)
1.定义student类 2.定义方法 public List<Student> findAll() 3.实现方法 select *from student
import domain.Student; import java.sql.*; import java.util.ArrayList; import java.util.List; /** * 2020/1/30 */ public class JDBCDemo2 { public static void main(String[] args) throws ClassNotFoundException { List<Student> list=findAll(); System.out.println(list); System.out.println(list.size()); } public static List<Student> findAll() throws ClassNotFoundException { List<Student> list=null; Class.forName("com.mysql.jdbc.Driver"); try { list = new ArrayList(); Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/exercise", "root", "123456"); String sql1 = "select * from student"; Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(sql1); Student student = null; while (resultSet.next()) { int sid = resultSet.getInt(1); String sname = resultSet.getString(2); int age = resultSet.getInt(3); String ssex = resultSet.getString(4);//创建student对象 student = new Student(); student.setSid(sid); student.setSname(sname); student.setSage(age); student.setSsex(ssex); list.add(student); } statement.close(); connection.close(); } catch (SQLException e) { e.printStackTrace(); } return list; } }
有多数重复代码,所以将jdbc工具类封装起来,可以将固定的路径与表放在配置文件中,然后该类加载配置文件:
package JdbcExercise; import java.io.FileReader; import java.io.IOException; import java.net.URL; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; /** * 2020/1/30 * 抽取jdbc常用的放在工具类里面 */ public class JdbcUtils { private static String url; private static String user; private static String password; private static String driver; /** * 静态代码块负责读取配置文件获取数据库连接的各种参数,只读取一次 * Properties集合类来获取配置文件 */ static { try { Properties p=new Properties(); //方式一:加载文件(可能会因路径写错而报异常:FileNotFoundException) // p.load(new FileReader("src/jdbc.properties")); //方式二:获取src路径下的文件,标准方式-->获取ClassLoader类加载器 /** * 步骤:先通过字节码文件获取class它的类加载器;sun.misc.Launcher$AppClassLoader@18b4aac2 * 通过该加载器获取它的文件名称资源,得到一个URL统一资源定位符;
file:/C:/Users/laurarararararara/IdeaProjects/SqlTest/out/production/SqlTest/jdbc.properties * 再通过getPath方法获取该文件的路径,得到一个String类型的地址;
/C:/Users/laurarararararara/IdeaProjects/SqlTest/out/production/SqlTest/jdbc.properties * load方法加载该地址。 */ ClassLoader classLoader = JdbcUtils.class.getClassLoader(); System.out.println(classLoader); URL resource = classLoader.getResource("jdbc.properties"); System.out.println(resource); String path = resource.getPath(); System.out.println(path); p.load(new FileReader(path)); //获取属性赋值 url=p.getProperty("url"); user=p.getProperty("user"); password=p.getProperty("password"); driver=p.getProperty("driver"); } catch (IOException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url,user,password); } public static void close(Statement statement,Connection connection) { if(statement!=null){ try { statement.close(); } catch (SQLException e) { e.printStackTrace(); } } if(connection!=null){ try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
测试的Demo类:
练习2:Jdbc的事务操作
使用connection对象来管理事务
开启事务:connection.setAutoCommit(false); 在执行sql前开启事务
提交事务:commit( ); 当所有sql都执行完提交事务
回滚事务:rollback( ); 在catch中回滚事务
正常情况下的代码操作,完成后,money会相应改变
可能出现的问题:
事务处理后:
package JdbcExercise; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; public class JdbcAffairs { public static void main(String[] args) { Connection connection=null; try { connection = JdbcUtils.getConnection(); //开启事务 connection.setAutoCommit(false); String sql1="update account set money= money-? where name=?"; String sql2="update account set money= money+? where name=?"; PreparedStatement preparedStatement1 = connection.prepareStatement(sql1); PreparedStatement preparedStatement2 = connection.prepareStatement(sql2); preparedStatement1.setInt(1,500); preparedStatement1.setString(2,"a"); preparedStatement2.setInt(1,500); preparedStatement2.setString(2,"b"); preparedStatement1.executeUpdate(); //手动制造异常 int i=3/0; preparedStatement2.executeUpdate(); //提交事务 connection.commit(); JdbcUtils.close(preparedStatement1,connection); JdbcUtils.close(preparedStatement2,null); } catch (Exception e) { //如果出现任何异常要用catch语句抓,所以回滚操作放在catch;同时应注意异常的抓应该为大的Exception try { connection.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } e.printStackTrace(); } } }
补充: Spring JDBC:Spring框架对JDBC的简单封装
queryForMap():查询结果封装为map集合
queryForList():查询结果封装为list集合(将每一条记录封装为一个map集合,再装载到list集合中)
queryForObject():查询结果封装为对象
query():查询结果封装为javaBean对象
导入包错误异常:
map集合只能放一条记录,列名为key,值为value:
query方法的两种参数:
queryForObject方法:
package JdbcTemplate; import org.junit.Test; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import utils.DruidUtils; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import java.util.Map; /** * 2020/2/7 JdbcTemplate对象的方法 * 不需要close */ public class TemplateDemo1 { static JdbcTemplate template =new JdbcTemplate(DruidUtils.getDs());; public static void main(String[] args) { test3(); System.out.println(); test(); test1(); System.out.println(); test2(); } public static void test3(){ //1.导入jar包as libraries 2.创建JdbcTemplate对象 3.调用方法 String sql = "update account set money=1200 where id=?"; int count = template.update(sql, 3); System.out.println(count); // //注意该方法结果集的长度只能是一条,将列名作为key,值作为value,封装为map集合 // String sql2 = "select name,money from account where id=? or id=?"; // Map<String, Object> stringObjectMap = template.queryForMap(sql2, 1, 2); // System.out.println(stringObjectMap); //普通的list查询一条记录,可以设置泛型将每一条记录放在一个map集合,然后把该集合放在list集合 String sql3 = "select * from account"; List<Map<String,Object>> list = template.queryForList(sql3); for (Map<String,Object> stringObjectMap1:list) { System.out.print(stringObjectMap1+" "); } } //没有简化,实现了RowMapper接口还是跟之前相同 public static void test(){ String sql4 = "select * from account"; List<Account> list1 = template.query(sql4, new RowMapper<Account>() { @Override public Account mapRow(ResultSet resultSet, int i) throws SQLException { Account account=new Account(); int id = resultSet.getInt(1); String name=resultSet.getString(2); int money = resultSet.getInt(3); account.setId(id); account.setName(name); account.setMoney(money); return account; } }); for (Account account:list1) { System.out.print(account+" "); } System.out.println(); } //用BeanPropertyRowMapper对象,构造函数中加载类的字节码文件;可以实现到javaBean的自动封装 public static void test1() { String sql4 = "select * from account"; List<Account> list = template.query(sql4, new BeanPropertyRowMapper<Account>(Account.class)); for (Account account:list) { System.out.print(account+" "); } } public static void test2() { //返回查询记录总数;queryForObject用于聚合函数的查询 String sql4 = "select count(money) from account"; //返回的是long型 Integer integer = template.queryForObject(sql4, Integer.class); System.out.println(integer); //返回记录Account对象的形式 String sql5="select *from account where money=?"; Account account=template.queryForObject(sql5,new BeanPropertyRowMapper<Account>(Account.class),1200); System.out.println(account.toString()); } }