反射reflect
深入的细分类的加载,当程序启动时候使用到某个类,如果该类为被加载到内存中,则JVM会通过是三个步骤进行类的初始化[加载、连接、初始化
什么是Class对象?
如果Java中我们需要创建某个类的对象
Student stu = new Student(); --> 通过Student类创建stu对象,stu对象是Student类的实例,在创建过程中隐藏内容的一些细节,Student类中所存在[属性,方法,修饰符,构造方法]等等
Java是一门面向对象的语言,面向对象核心思想"世间万物皆对象",在看待承载Student类所提供信息时,提供一个可以描述类来描述student类,将类Student看做是类对象,Java专门提供了一个可以描述类的类,这个类就是Class类,Class类是所有类的类对象集合即(类被加载到JVM时会被创建class对象就是Class这个类实例),通过Class类创建出来对象来描述当前类
Class类就是类的.class文件加载到JVM中所创建class对象的描述,Class就是所有类的描述类(描述类的类),既然Class类可以描述类的类,并且可以获得你当前类的.class文件对象即class对象,我们就可以通过这个class对象创建出当前类的类的对象,这个技术就叫做 "反射"
例如:
正常创建对象 --> Student stu = new Student();
但是在JVM中是先的到Student类的.class文件,创建出Student类的class对象
class对象是不是就代表着Student类,就可以通过"class对象"反向的创建出Student类的对象,这个过程就叫做"反射"
class对象是类的字节码文件对象即(.class文件),既然class是对象,需要有一个可以描述class对象类,这个类就是Class类
如何获取Class类的对象
`package com.qfedu.reflect;
public class ReflectInstance { public static void main(String[] args) throws Exception { //获取Class类的对象-->获取Student类的class对象 //1.正常创建Student类的对象 Student stu = new Student(); stu.name = "张三"; System.out.println(stu.name);
//通过反射获取到Student这类的class对象
//1.通过Class类中静态方法 forName来获取Student类的对象
//参数: 类的全限定名称 ---> 包名+类名
Class aClass = Class.forName("com.qfedu.reflect.Student");
//此时aClass对象就代表着Student类,利用aClass反向的创建出Student类的对象
Object o = aClass.newInstance(); //创建对象
System.out.println(o instanceof Student);
//2.Java中每个类都有一个静态方法,Class类是描述类的类,那么所有类都是Class类的实例(即class对象)
//每个类都可以直接调用class静态方法获取哦Class类的对象
Class<Student> studentClass = Student.class;
Student student = studentClass.newInstance();
//3.现场创建Student类的对象
Student student1 = new Student();
//3.1通过student1对象调用父类Object所提供getClass方法获取Class类的对象
Class aClass1 = student1.getClass();
Object o1 = aClass1.newInstance();
//强烈推荐第一种,它不受.java源文件的影响,即.java源文件不存在也可以创建(只是报错而已)
}
}
JavaSE中有就打内置Class对象[他们是JVM预先加载好的Class实例]
byte、short、int、long、float、double、char、boolean、void
其中八种基本数据类型对应包装类中提供一个静态属性Type,这个Type就是基本数据类型对应的class对象
Integer.Type 等价于 int.class
数组支持获取class对象
数据类型[].class 或者 数组对象.getClass()
反射操作实例
反射其实可以帮助做很多操作,通用性方法的封装,通过反射获取到类中所有属性和反方,无视权限修饰符,利用反射提供设计模式(静态和动态代理),在不破坏原有类的结构的前提下,可以对类增加新的方法,反射可以帮组我们加载资源文件
演示反射如何操作类
package com.qfedu.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
//通过反射获取student类对象,并同类对象创建Student类的对象,操作类中方法和属性
public class ReflectStudentInstance {
public static void main(String[] args) throws Exception {
//0.先获取到Student类的class对象
Class aClass = Class.forName("com.qfedu.reflect.Student");
//直接创建这个Student类对象的方式 --> newInstance() 相当于间接调用Student类中无参构造方法
//1.通过反射获取类中构造方法
/*
通过class对象调用getConstructor方法可以获取到一个构造方法的对象(Constructor类的对象)
在利用这个构造方法对象创建出我们所需要类的对象
一类中是存在多个构造方法,如何确定或如何指定获取到那个构造方法对象
getConstructor方法中的参数有关,这个方法中参数是Class类型可变参数
getConstructor(Class<?>... parameterTypes)
如果不传递参数数据,此时获取的就是类中无参构造方法
如果传递参数数据,此时获取的就是与参数匹配的对应构造方法
PS:因为参数类型是Class类型,所以传递参数时,需要传递构造方法数据类型的class对象即
*/
Constructor constructor = aClass.getConstructor();
/*
不能直接通过Constructor对象创建类的对象,需要用过Constructor对象调用一个方法
newInstance方法才可以创建类的对象
newInstance(Object ... initargs) 使用了可变参数
newInstance方法的参数传递和getConstructor这个方法有关,如果getConstructor方法传递了参数
那么在调用newInstance的时候就必须传递参数
newInstance这个方法如果不传递方法参数,使用就是无参构造方法
newInstance这个方法传递方法参数,使用就是有参方法,参数就是具体对构造方法参数的赋值
*/
Object o = constructor.newInstance();
System.out.println("o这个对象是Student类型吗?"+(o instanceof Student));
//有参构造方法获取
Constructor constructor1 = aClass.getConstructor(String.class, int.class);
Object o1 = constructor1.newInstance("张三", 18);
Student stu = (Student)o1;
System.out.println("姓名:"+stu.name);
//age' has private access in 'com.qfedu.reflect.Student' 私有属性在外界是无法获取的
//System.out.println("年龄:"+stu.age);
//反射在获取类中属性或方法时,是无视权限修饰符的
//利用反射获取私有构造方法创建对象
// -->只要通过class对象调用方法时,如果发现方法中使用Declared单词的方法
// --> 暴力反射方法 无视权限修饰符获取对应的方法或属性
Constructor declaredConstructor = aClass.getDeclaredConstructor(String.class);
//private权限是一个比较特殊的权限,因为权限等级是最低,为了方式使用反射恶意暴力反射
//需要在访问private权限修饰符的方法或属性时,需要开启权限操作
declaredConstructor.setAccessible(true);//开始权限访问
Object o2 = declaredConstructor.newInstance("李四");
System.out.println("o2是Student类的对象吗?"+(o2 instanceof Student));
/*
如果你要获取构造方法
如果构造方法时候用public修饰 --> getConstructor
如果构造方法是非public修饰 --> getDeclaredConstructor
如果碰到使用这个方法还无法操作,一定要开启权限操作 通过方法获取的对象调用.setAccessible(true);
*/
//2.获取类中属性
/*
通过Class对象获取类中属性
如果类中属性是public修饰 ---> getFiled
如果类中属性是非public修饰 ---> getDeclaredFiled
如果碰到使用这个方法还无法操作,一定要开启权限操作 通过方法获取的对象调用.setAccessible(true);
*/
//获取Student类中name属性操作权
//参数是属性名字.返回的是Filed对象
Field name = aClass.getField("name");
//取值操作,通过Filed对象调用get方法就可以获取属性值
//get方法方法中需要一个参数,这个参数是Student类的对象(通过newInstance创建对象)
System.out.println(name.get(stu));
//赋值操作,通过Filed对象调用set方法就可以对属性赋值
/*
第一个参数:是Student类的对象(通过newInstance创建对象)
第二个参数:是对属性赋值
*/
name.set(stu,"王五");
System.out.println(name.get(stu));
/*
如果获取非public权限属性,只需要改变获取方法其他不需要改变
如果你获取的是static修饰属性,将面临set方法的第一个参数和get方法的参数传递问题
如果是static属性,只要传递为null即可
在反射中如果传递类的对象的位置传递null就代表static修饰符静态属性或方法
name.set(null,"王五");
System.out.println(name.get(null)); ---> 代表是静态属性
*/
// 类中方法(重点记忆)
/*
通过Class对象获取类中属性
如果类中方法是public修饰 ---> getMethod
如果类中方法是非public修饰 ---> getDeclaredMethod
如果碰到使用这个方法还无法操作,一定要开启权限操作 通过方法获取的对象调用.setAccessible(true);
获取方法对象getMethod方法参数问题
第一个参数是: 获取方法的名字
第二个参数是: Class类型可变参数
如果不对可变参数赋值,调用就是没有参数的方法
如果对可变参数赋值,赋值方式就是方法参数类型的class对象,调用就是有参数的方法
*/
Method show = aClass.getMethod("show");
Method show1 = aClass.getMethod("show", int.class, String.class);
//此时获取的是方法对象,需要执行方法需要调用方法中提供invoke
/*
invoke方法和getMethod方法是对应的,如果getMethod中没设置参数,invoke就没有参数
如果getMethod方法设置了参数就需要对调用时对进行参数赋值
invoke方法第一个参数,如果是成员方法就传递一个是Student类的对象(通过newInstance创建对象)
如果是静态方法第一个参数就是null
*/
show.invoke(stu);
show1.invoke(stu,1,"王五");
}
}
利用反射可以直接加载Properties文件对象
在IDEA中是可以创建出不同类型的文件夹,已知创建出来一种文件夹是"普通文件夹",除了这个文件夹之外,我们还可以创建,"资源文件夹Source Folder", "这个文件夹的特点是可以将文件夹中文件加载到classPath路径"
classPath路径就是工程中.java文件生成.class文件的路径,利用找那个原则就可以获取到Properties资源文件
package com.qfedu.reflect;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
//使用反射读取资源文件
public class ReflectReadProperties {
public static void main(String[] args) throws IOException {
//1.创建资源文件对象
Properties properties = new Properties();
//2.提供一个类的加载器,类加载器可以加载资源获获取流
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
//任何类都有ClassLoader,上面这种写法方式习惯性,如果是本类获取
// ClassLoader classLoader = ReflectReadProperties.class.getClassLoader();
//3.ClassLoader加载文件获取流 ,参数直接传递文件名即可
InputStream resourceAsStream = contextClassLoader.getResourceAsStream("MyProperties.properties");
//4.加载文件内容到对象中
properties.load(resourceAsStream);
System.out.println(properties);
}
}
封装DBUtil工具类
之前这已经使用DDL、DML和DQL语言进行JDBC编程操作,但是可以发现代码中出现大量重复性操作,其实就触发了软件开发中的一个原则[DRY] (不要写重重复性的代码 ) ,所以就需要将这些重复性代码进行抽取和封装,值提供一次实现,外界只需要调用实现就可以完成操作,此时就需要提供一个工具类,这个类封装着JDBC中常用操作,这个类就叫做"DBUtil"工具类
提供DBUtil工具类封装
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Objects;
import java.util.Properties;
//对JDBC中常用操作的封装类
/*
工具类特点:这个类中所有属性和方法都是静态的,并且工具类不允许创建对象
*/
public class DBUtil {
private DBUtil(){} //防止外界创建对象
/*
为了保证驱动加载的执行速度,提供一个静态代码块 随着DBUtil类使用加载驱动
在获取Connection对象时候,需要配置JDBC的连接参数,如果这个参数写死在方法中是否合理?
这个配置肯定不合理,如果连接数据库的操作发生改变,就修改源代码
提供工具类目的就是通用性,如果每次都要源代码才能保证修改连接方式,还是麻烦
即不在代码中提供,有可以可以在代码操作,此时就可以利用到文件
将配置信息写入到一个文件中,使用时候只要读取文件,加载配置信息,如果修改连接操作
无需修改代码,只要修改替换文件即可,需要一种可以保证读取便捷获取配置的文件 --> properties文件
*/
//因为数据已经写入到资源文件中,所以加载资源文件到Properties中
private static final Properties p = new Properties();
static {
//0.添加资源文件的加载操作
try {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
InputStream resourceAsStream = loader.getResourceAsStream("jdbc.properties");
p.load(resourceAsStream);
} catch (IOException e) {
throw new RuntimeException("加载classPath路径下properties文件出现问题:"+e.getMessage());
}
//1.加载注册驱动
try {
Class.forName(p.getProperty("driver"));
} catch (ClassNotFoundException e) {
throw new RuntimeException("加载注册驱动失败:"+e.getMessage());
}
}
/**
* 提供Connection对象获取(获取连接对象)
* @return connection连接对象
*/
public static Connection getConnection(){
try {
//不出问题就是正常创建
return DriverManager.getConnection(
p.getProperty("url"),p.getProperty("username"),p.getProperty("password")
);
} catch (SQLException e) {
throw new RuntimeException("数据库连接对象创建失败:"+e.getMessage());
}
}
/**
* JDBC连接资源的统一释放曹组
* @param connection 连接对象
* @param statement 静态语句对象
* @param resultSet 结果集对象
*/
public static void closeAll(Connection connection, Statement statement, ResultSet resultSet){
try {
if(Objects.nonNull(connection)){
connection.close();
}
if(Objects.nonNull(statement)){
statement.close();
}
if(Objects.nonNull(resultSet)){
resultSet.close();
}
} catch (SQLException e) {
throw new RuntimeException("数据库释放资源对象失败:"+e.getMessage());
}
}
}
JDBC资源文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mydb1
username=root
password=123456
为什么不分装SQL语句操作,因为不用操作中SQL语句是有变化的,现在使用静态语句,后续会使用预编译语句,在后续会使用BDUtils工具类,后续会在使用MyBits,语句是不断变化,操作也是不断变化,所有你不建议封装
实现登录和注册案例
创建一张用户表 User
- id ,主键、自动增长。
- 用户名,字符串类型,唯一、非空
- 密码,字符串类型,非空
控制台上输出 1.注册 和 2.登录
- 注册和登录都需要通过控制台用户输入用户名和密码。 注册是需要验证用户名和密码是否合法 --》 要求 字母数字组成,并且第一个字符必须为字母 6~16位 如果合法在验证用户名是否存在在数据库中, 如果存在则提示注册失败,否则注册成功!
在弹出 1.注册 和 2.登录 登录不需要验证了直接进入到数据库查询验证操作, 如果该用户存在,提示登录成功,反之提示登录失败。
import com.qfedu.util.DBUtil;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
//JDBC创建一张user表
public class CreateTable {
public static void main(String[] args) throws SQLException {
//1.提供静态SQL语句
String sql = "create table user(" +
" id int primary key auto_increment," +
" username varchar(20) unique not null," +
" password varchar(20) not null" +
")";
//2.获取Connection对象 --> 使用的就是封装好的DBUtil工具类
Connection connection = DBUtil.getConnection();
//3.创建Statement对象
Statement statement = connection.createStatement();
//4.通过Statement对象发送SQL语句到数据库中执行
//建表语句属于一个特殊的DDL语句,所以返回值是0 也代表建表成功
int i = statement.executeUpdate(sql);
System.out.println("建表成功");
DBUtil.closeAll(connection,statement,null);
}
}
import com.qfedu.util.DBUtil;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;
//登录注册操作
public class LoginAndRegister {
public static void main(String[] args) throws SQLException {
//1.提供控制台获取数据操作
Scanner input = new Scanner(System.in);
//2.获取Connection对象
Connection connection = DBUtil.getConnection();
//3.提供一个循环完成主体执行逻辑
EXIT_WHILE:while(true){
System.out.println("1.登录\t\t2.注册");
int num = input.nextInt();
switch (num){
//登录逻辑
case 1:
//3.1获取控制台输入的数据
System.out.println("请输入用户名:");
String username = input.next();
System.out.println("请输入密码:");
String password = input.next();
/*
这里最不可取的一种执行语句就是直接查询全表即User
因为我们已经获取到了用户名和密码,利用用户名和密码直接查询数据库
静态语句的一个问题,静态语句对建库和建表操作是比较友好,语法是定死的没有动态数据
但是静态语句对增删改查不是很友好,一般面临需要在SQL语句中和拼接上动态数据,非常麻烦
*/
StringBuilder bs = new StringBuilder("select * from user where username =");
bs.append("'").append(username).append("'")
.append(" and password =").append("'").append(password).append("'");
//3.2创建Statement对象
Statement statement = connection.createStatement();
//3.3执行查询语句
ResultSet resultSet = statement.executeQuery(bs.toString());
/*
resultSet是DQL语言查询数据库返回结果集的映射,resultSet对象就代表着查询出来结果
判断用户输入用户名和密码是否正确,查询语句的构造成已经将用户名和密码传递到数据库中进行
查询,resultSet结果应该就是两种
第一种查询到,resultSet中有结果集数据
第二种没有查询到,resultSet中结果集数据null(空的)
*/
if(resultSet.next()){
System.out.println("登录成功");
DBUtil.closeAll(null,statement,resultSet);
}else{
System.out.println("登录失败[输入的用户名和密码错误!]");
DBUtil.closeAll(null,statement,resultSet);
}
break EXIT_WHILE;
//注册逻辑
case 2:
//注册失败让你继续注册,注册成功为止
while(true){
//1.获取控制台上的用户名和密码
System.out.println("请输入用户名:");
String username1 = input.next();
System.out.println("请输入密码:");
String password1 = input.next();
//验证用户名和密码输入是否和法 ---> 第一个字符必须是字母长度6~16位
if(username1.matches("[a-zA-z]\\w{5,15}") && password1.matches("[a-zA-z]\\w{5,15}")){
//验证用户名是否被注册
StringBuilder bs1 = new StringBuilder("select * from user where username = ");
bs1.append("'").append(username1).append("'");
//创建Statement对象发送SQL语句执行得到返回结果集
Statement statement1 = connection.createStatement();
ResultSet resultSet1 = statement1.executeQuery(bs1.toString());
if(resultSet1.next()){
System.out.println("您输入用户名已经被注册,请从新输入");
DBUtil.closeAll(null,statement1,resultSet1);
}else{
//插入数据
String sql = "insert into user(username,password) " +
"values("+"'"+username1+"',"+"'"+password1+"')";
Statement statement2 = connection.createStatement();
int i = statement2.executeUpdate(sql);
if(i > 0){
System.out.println("恭喜注册成功!");
break;
}
DBUtil.closeAll(null,statement2,null);
}
}else{
System.out.println("您输入的用户名和密码不和法请从新输入");
}
}
break;
}
}
DBUtil.closeAll(connection,null,null);
}
}
预编译语句
之前学习的静态SQL语句在没有输入数据(动态数据)的时候,使用起来还是比较方便,但是如果一旦需要外界数据传入(动态数据),就需要对静态SQL语句进行拼接操作操作,如果参数特别多,拼接语句就会十分麻烦而且还容易拼接错误
静态SQL语句是无法防止"SQL注入"的,只能使用预编译SQL语句来防止SQL注入
Java就提供了一种方便操作,并且可以防止"SQL注入",而且还有SQL语句优化操作的一个语句,这个语句就叫做"预编译语句"
JDBC中提供了预编译语句是Statement接口的子接口他们是继承关系,并且预编译语句的接口名PreparedStatement
因为PreparedStatement是Statement的子接口,所以继承父类接口中操作DDL,DML和DQL的执行方法,依旧是使用executeUpdate()和executeQuery()这个两个方法执行DDL,DML和DQL语言,但是略有不同
预编译语句和静态语句最大的区别在于,预编译语句中数据是以 [?] 占位符进行占位,在执行语句阶段在进行赋值操作者,而不是想静态SQL语句一样直接将数据写好
静态SQL语句
insert into user(username,password) values('zhansgan','123456');
预编译SQL语句
insert into user(username,password) values(?,?);
后再后续SQL语句执行时对 [?] 占位符进行赋值操作
PreparedStatement提供一个方法专门针对[?]占位符进行赋值操作
setXXX(int parameterIndex,XXX value)
XXX代表你要赋值的列的数据类型
例如:上面的预编译语句insert into user(username,password) values(?,?);
username在SQL中是varchar类型 所以在Java中对应String类型
调用setString(int parameterIndex,"张三")
PreparedStatement这个语句是使用 [?] 占位符进行占位,如何指定对那个 [?] 占位符进行赋值
在一个预编译SQL语句中 ? 占位符是按照从左向右的方向进行标记记位,最左边第一个 [?] 占位符的位置是1,依次向右类推,逐渐递增+1
完整的对预编译SQL语句中[?]占位符的赋值是:
例如:上面的预编译语句insert into user(username,password) values(?,?);
username在SQL中是varchar类型 所以在Java中对应String类型
调用setString(1",张三") --->相当于是对 username进行赋值(左边第一个?)
调用setString(2,"123456") --->相当于是对 password进行赋值(第二个?)
简单模拟一个SQL注入操作
import com.qfedu.util.DBUtil;
import java.sql.*;
public class SQLToIN {
public static void main(String[] args) throws SQLException {
//使用静态SQL语句,完成对user表登录操作
/* *//*
登录成功原因就是在于 静态SQL语句的问题,将整个SQL语句看做是一个整体
并且进行where条件判断
username = '' 整个结果必然是false
使用 AND 查询 false结果登录失败 查询不到
OR 1=1 此时这个判断 false OR 1=1 --> 1=1 true 结果是true
password = 'a123456' 结果为true true And true 登录成功
*//*
String staticSQL = "select * from user where username = '' OR 1=1 AND password='a123456'";
//进行获取连接操作并且执行SQL语句
Connection connection = DBUtil.getConnection();
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(staticSQL);
if(resultSet.next()){
System.out.println("登录成功");
}else{
System.out.println("登录失败");
}
DBUtil.closeAll(connection,statement,resultSet);*/
//使用预编译SQL语句完成user表的登录操作
String preSQL = "select * from user where username = ? AND password = ?";
//1.先获取到Connection对象
Connection connection = DBUtil.getConnection();
//2.通过Connection对象创建预编译语句对象
// 调用的预编译语句对象的创建时需要传入参数的,参数就是预编译SQL语句
PreparedStatement preparedStatement = connection.prepareStatement(preSQL);
//3.通过预编译语句对象提供赋值语句对[?]占位符进行赋值
preparedStatement.setString(1,"'' OR 1=1");
preparedStatement.setString(2,"a123456");
//通过预编译语句对象调用executeQuery()和 executeUpdate()都不需要参数
ResultSet resultSet = preparedStatement.executeQuery();
if(resultSet.next()){
System.out.println("登录成功");
}else{
System.out.println("登录失败");
}
DBUtil.closeAll(connection,preparedStatement,resultSet);
/*
预编译语句会将赋值操作参数看做是一个整体 即认为 '' OR 1=1 是一个整体字符串
即认为是用户名 生成SQl语句
select * from user where username = ''' OR 1=1' and password = 'a123456';
*/
}
}
综上所述:在以后开发汇中如果要使用JDBC进行数据开连接开发,建议编写预编译SQL语句,不见可以方便我们的动态赋值,也可以防止SQL注入,并且具有SQL语句优化的特点,预编译SQL语句可以使用在增删改查上,静态SQL语句可使用在建库,建表,删库和删表上(预编译SQL语句也可以)
DAO数据访问对象(Date Access Object)
在接下来的开发中,我们要创建很多很多不同功能类,所以我们为了方便管理会将这些类进行分层(分包)存储,包的分配会根据类作用来进行定义,随着人们不断的对类进行分包存储包名定义,从而总结出一个编程模式,这个条模式叫做 [三层架构] (三层架构就是MVC设计模式的前身)
什么是三层架构?
人为的对代码分层管理,这样的管理利于开发人员对每个层级中代码进行维护与开发,代码与代码之间存在一个人为的分层,模拟了现实生活中的一些逻辑,一层只做一件事,每层分工合作完成整个代码
编写数据访问层 --> DAO
在编写操作数据库的代码时就有命名要求了,包名必须是以dao最为包名,代表这个包是数据访问层级即dao层
例如: com.qfedu.Emproject.dao包名 -->证明和这个dao包是Empproject项目的dao层即数据访问层
在包中创建类名也是有要求的,对数据库一个表的操作对应一套 XXXXDao 和 XXXXDaoImpl
PS:开发中XXXX是要操作表的名字
XXXXDao是接口定义操作表中方法 XXXXDaoImpl是定义接口的实现类
为什么要提供一个接口,而不是在类中直接定义方法的实现?
1.对于后续学习开发框架契合
2.在实际开发中是多人协同开发,在写其他service层的开发人员,不会等你将dao层都写完在开发,需要service层中使用dao层操作就需要有一个"占位方式",service层只要调用你接口中定义方法可以,无需等待你完全开发完毕在调用.
3.提供接口好处在于方便提供不同实现方式,针对某些方法进行重新实现(一个接口多种实现的效果了)
DAO层核心就是提供对数据库CRUD操作,所以在定义接口方法时候无非就是定义对数据库执行操作方法,但是也要结合service层需要的操作在进行定义,方能达到通用性
实体类
Dao层是操作一张表达过程,操作表时,就需要有数据传递和存储,传递主要针对的就是DML操作,存储主要针对的就是DQL操作,提供这个实体类就是为了方便对数据进行存储操作使用,通过DQL操作将数据查询出来存储在Resultset结果集对象中是零散,需要将这些零散数据进行统一从存储,并可以加以操作,就需要提供一个存储方式,这个方式就是"实体类"
实体类的创建也是有规则并且也有自己的包entity,这个包下存储都是实体类
实体类创建的规则就是和表之间形成一个完整的映射关系(ORM)
类名 ---> 表名 类属性 ---> 表中列 类属性数据类型 ---> 表中列的数据类型
表中每一行数据 ----> 类的每一个对象
既然为了封装这些零散数据为了方便使用和存储,所以在设计实体类时候就满足一些需求,这个类也被称之为"Java Bean"
如果某个类被称为Java Bean即实体类需要满足以下需求:
1.类必须使用public修饰
2.类必须保证有public修饰的有参和无参构造方法
3.包含处理属性的全部手段[属性私有化,提供getter和setter]
这个样类就是一个实体类即Java Bean,这类一般不会提供一些方法(成员或静态),这个类也不会作为实现接口类,这个类要求,类名尽量与操作的表名一致,属性名尽量要与列名一致(最好是完全一致,方便后续操作)
编写一个程序,完成DAO层定义与操作
创建一个Person表
id int primary key auto_increment, -- id主键列
name varchar(20) not null, -- 人名字
age int not null, -- 年龄
birthday date, -- 生日
email varchar(100), -- 邮箱
address varchar(255) -- 地址
);
对这张表完成DAO层操作
1.创建person表是实体类,提供表的数据存储操作
import java.util.Date;
//Person表的实体类
public class Person {
//表中列就是类中属性
private int id;
private String name;
private int age;
private Date birthday;
private String email;
private String address;
public Person() {
}
public Person(int id, String name, int age, Date birthday, String email, String address) {
this.id = id;
this.name = name;
this.age = age;
this.birthday = birthday;
this.email = email;
this.address = address;
}
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 int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
//如果向观察实体类中存储数据是否正确,建议从写toString
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", birthday=" + birthday +
", email='" + email + '\'' +
", address='" + address + '\'' +
'}';
}
}
2.编写dao层操作
2.1定义Person表操作的接口
import com.qfedu.entity.Person;
import java.sql.SQLException;
import java.util.List;
/*
这个接口定义的就是操作Person表的方法
接口名字命名 ---> I 代表接口 Person 代表是操作表的名字
---> Dao代表这个接口是dao层的
*/
public interface IPersonDao {
/**
* 向Person表中插入数据
* @param person 实体类的对象封装这要添加到表中数据
* @return 大于0 证明插入成功,否则插入失败
* @throws SQLException 处理SQL的异常
*/
int insert(Person person)throws SQLException;
/**
* 向Person表中更新数据
* @param person 实体类的对象封装这要添加到表中数据
* @return 大于0 证明插入成功,否则插入失败
* @throws SQLException 处理SQL的异常
*/
int update(Person person)throws SQLException;
/**
* 通过ID删除表中单条数据
* @param id 根据表中id列进行删除
* @return 大于0 证明插入成功,否则插入失败
* @throws SQLException 处理SQL的异常
*/
int delete(int id)throws SQLException;
/**
* 通过ID查询表中单条数据
* @param id 表中的主键列
* @return 封装着结果集的数据Person对象
* @throws SQLException
*/
Person select(int id)throws SQLException;
/**
* 查询整个Person表中的数据
* @return List集合存储的是这个Person表中所有数据即Person实体类对象
* @throws SQLException
*/
List<Person> selectAll()throws SQLException;
}
2.2实现Dao接口
import com.qfedu.dao.IPersonDao;
import com.qfedu.entity.Person;
import com.qfedu.util.DBUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
//对提供接口中方法的实现类
public class IPersonDAOImpl implements IPersonDao {
@Override
public int insert(Person person) throws SQLException {
//1.提供预编译SQL语句
String sql = "insert into person(name,age,birthday,email,address) values(?,?,?,?,?)";
//2.创建Connection对象
Connection connection = DBUtil.getConnection();
//3.构建预编译语句对象
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//4.通过预编译语句对象中赋值操作对占位符进行赋值操作
preparedStatement.setString(1,person.getName());
preparedStatement.setInt(2,person.getAge());
//如果将java.util.Date对象存储到数据库中,需要转换为java.sql.Date才可以存储
preparedStatement.setDate(3,null);
preparedStatement.setString(4,person.getEmail());
preparedStatement.setString(5,person.getAddress());
int i = preparedStatement.executeUpdate();
DBUtil.closeAll(connection,preparedStatement,null);
return i;
}
@Override
public int update(Person person) throws SQLException {
//1.提供预编译SQL语句
String sql = "update person set name = ?,age = ?,birthday = ?," +
"email = ?,address = ? where id = ?";
//2.获取连接对象
Connection connection = DBUtil.getConnection();
//3.获取预编译语句对象
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//4.通过预编译语句对象对占位符进行赋值操作
preparedStatement.setString(1,person.getName());
preparedStatement.setInt(2,person.getAge());
preparedStatement.setDate(3,null);
preparedStatement.setString(4,person.getEmail());
preparedStatement.setString(5,person.getAddress());
preparedStatement.setInt(6,person.getId());
int i = preparedStatement.executeUpdate();
DBUtil.closeAll(connection,preparedStatement,null);
return i;
}
@Override
public int delete(int id) throws SQLException {
String sql = "delete from person where id = ?";
Connection connection = DBUtil.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1,id);
int i = preparedStatement.executeUpdate();
DBUtil.closeAll(connection,preparedStatement,null);
return i;
}
@Override
public Person select(int id) throws SQLException {
String sql = "select * from person where id = ?";
Connection connection = DBUtil.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1,id);
ResultSet resultSet = preparedStatement.executeQuery();
Person person = null;
while(resultSet.next()){
int id1 = resultSet.getInt("id");
String name = resultSet.getString("name");
int age = resultSet.getInt("age");
Date birthday = resultSet.getDate("birthday");
String email = resultSet.getString("email");
String address = resultSet.getString("address");
person = new Person(id1,name,age,birthday,email,address);
}
DBUtil.closeAll(connection,preparedStatement,resultSet);
return person;
}
@Override
public List<Person> selectAll() throws SQLException {
String sql = "select * from person ";
Connection connection = DBUtil.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql);
ResultSet resultSet = preparedStatement.executeQuery();
List<Person> list = new ArrayList<>();
while(resultSet.next()){
int id1 = resultSet.getInt("id");
String name = resultSet.getString("name");
int age = resultSet.getInt("age");
Date birthday = resultSet.getDate("birthday");
String email = resultSet.getString("email");
String address = resultSet.getString("address");
Person person = new Person(id1,name,age,birthday,email,address);
list.add(person);
}
DBUtil.closeAll(connection,preparedStatement,resultSet);
return list;
}
}