Mybatis
1JDBC
JDBC 的开发步骤
1、注册驱动:主要告诉 JVM 我们的程序将要使用哪一种数据库
2、获取连接:使用 JDBC 中的类,获得数据库的连接对象 Connection
3、获得语句执行平台:通过 Connection 可以获取执行者对象,Statement、PreparedStatement.
4、执行 SQL 语句:使用执行者对象,向数据库中执行 SQL 语句,然后可以得到对应的接口,有单个结果,也可能有结果集 ResultSet。
5、处理结果
6、释放对象:关闭顺序:rs -> stmt 、ptmt -> conn
JDBC预处理对象prepareStatement概述
SQL注入问题
public class StatementMyCode {
public static void main(String[] args) throws SQLException {
Scanner sc=new Scanner(System.in);
System.out.println("请输入用户名:");
String username =sc.nextLine();
System.out.println("请输入密码:");
String password=sc.nextLine();
//获取JDBCUtils连接
Connection con=JDBCUtils1.getConnection();
//Connection con= JDBCUtils1.getConnection();
//获取Statedment对象
Statement stat=con.createStatement();
//执行SQL语句
String sql = "select * from users where username='"+username+"' and password ='"+password+"'";
System.out.println(sql);
ResultSet rs=stat.executeQuery(sql);
if(rs.next()){
System.out.println("登录成功!");
}else{
System.out.println("登录失败!");
}
JDBCUtils1.close(rs,stat,con);
}
}
SQL注入出现的登录BUG
SQL注入:用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正的意义。 假设有登录案例SQL语句如下: SELECT * FROM 用户表 WHERE NAME = 用户输入的用户名 AND PASSWORD = 用户输的密码; 此时,当用户输入正确的账号与密码后,查询到了信息则让用户登录。但是当用户输入的账号为XXX 密码为:XXX’ OR ‘a’=’a时,则真正执行的代码变为: SELECT * FROM 用户表 WHERE NAME = ‘XXX’ AND PASSWORD =’ XXX’ OR ’a’=’a’; 此时,上述查询语句时永远可以查询出结果的。那么用户就直接登录成功了,显然我们不希望看到这样的结果,这便是SQL注入问题。 为此,我们使用PreparedStatement来解决对应的问题。
原理介绍
preparedStatement:预编译对象,是Statement对象的子类。 特点:
-
性能高
-
会把sql语句先编译
-
能过滤掉用户输入的关键字。 PreparedStatement预处理对象,处理的每条sql语句中所有的实际参数,都必须使用占位符?替换。
用户登录界面代码演示 (此处省略了JDBCUtils工具类)
public class prepareStatementMyCode {
public static void main(String[] args) throws SQLException {
Scanner sc=new Scanner(System.in);
System.out.println("请输入用户名:");
String username =sc.nextLine();
System.out.println("请输入密码:");
String password=sc.nextLine();
//获取JDBCUtils连接
Connection con=JDBCUtils1.getConnection();
//Connection con= JDBCUtils.getConnection();
//获取Statedment对象
Statement stat=con.createStatement();
//执行SQL语句
String sql = "select * from users where username=? and password =?";
PreparedStatement ps = con.prepareStatement(sql);
ps.setObject(1,username);
ps.setObject(2,password);
System.out.println(sql);
ResultSet rs=ps.executeQuery();
if(rs.next()){
System.out.println("登录成功!");
}else{
System.out.println("登录失败!");
}
JDBCUtils1.close(rs,stat,con);
}
}
正确执行代码演示结果
SQL注入代码演示
JDBC 的简单使用
1、
@Test
public void fun1() throws Exception {
// 1.注册驱动
// 这里,其实是用到了反射的原理,通过给定类的名字,让程序自动去找对应的类
// 然后执行得到对应实例
/*
Class.forName("com.mysql.jdbc.Driver"):
其中一个原因是运行期以反射的方式来检查JDBC驱动的主类com.mysql.jdbc.Driver是否存在,这样的话当 我们知道了jar包不存在的时候我们就可以做更多的其他操作了
另外一个原因是:jdbc是Java的规范,就是为JDk提供了好多的接口,但是没有任何的实现,与之对应的自然 是java.sql.*,你的代码只是知道java.sql.*而并不知道com.mysql.*,com.oracle.*.我们现在用的 com.mysql.jdbc.Driver也是其中的东西,如果你想用原来的方式导入类的话(就是直接在上面写import com.mysql.jdbc.Driver)就违背了开闭原则(对拓展开放,对修改关闭)源代码里面有static块
*/
Class.forName("com.mysql.jdbc.Driver");
// 使用注册方法的时候,底层会注册两次,不推荐使用
// DriverManager.registerDriver(new com.mysql.jdbc.Driver());
// 2.获取连接:数据库地址、用户名、密码 Connection
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url, user, password);
// 3.获取执行者对象
Statement stmt = conn.createStatement();
// 4.准备 SQL 语句
String sql = "select * from product";
// 5.执行 SQL 语句
ResultSet rs = stmt.executeQuery(sql);
// 6.处理结果
while (rs.next()) {
int idStr = rs.getInt("id");
String nameStr = rs.getString("name");
double priceStr = rs.getDouble("price");
String markStr = rs.getString("mark");
System.out.println(idStr + "--" + nameStr + "--" + markStr);
}
// 7.关闭资源
rs.close();
stmt.close();
conn.close();
}
2、
Public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//通过驱动管理类获取数据库链接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "mysql");
//定义sql语句 ?表示占位符
String sql = "select * from user where username = ?";
//获取预处理statement
preparedStatement = connection.prepareStatement(sql);
//设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
preparedStatement.setString(1, "王五");
//向数据库发出sql执行查询,查询出结果集
resultSet = preparedStatement.executeQuery();
//遍历查询结果集
while(resultSet.next()){
System.out.println(resultSet.getString("id")+" "+resultSet.getString("username"));
}
} catch (Exception e) {
e.printStackTrace();
}finally{
//释放资源
if(resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(preparedStatement!=null){
try {
preparedStatement.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
上面代码有如下几个问题:
-
数据库连接,使用时创建,不使用就关闭,对数据库进行频繁连接开启和关闭,造成数据库资源的浪费
-
解决:使用数据库连接池管理数据库连接
-
-
将sql 语句硬编码到Java代码中,如果sql语句修改,需要对java代码重新编译,不利于系统维护
-
解决:将sql语句设置在xml配置文件中,即使sql变化,也无需重新编译
-
-
向preparedStatement中设置参数,对占位符位置和设置参数值,硬编码到Java文件中,不利于系统维护
-
解决:将sql语句及占位符,参数全部配置在xml文件中
-
-
从resutSet中遍历结果集数据时,存在硬编码,将获取表的字段进行硬编码,不利于系统维护。
-
解决:将查询的结果集,自动映射成java对象
-
2、Mybatis框架原理
什么是Mybatis
-
mybatis是一个持久层的框架,是apache下的顶级项目。
数据持久化
-
持久化就是将程序的数据在持久状态和瞬时状态转化的过程
-
内存:断电即失
-
数据库(Jdbc),io文件持久化
-
生活方面例子:冷藏,罐头。
-
为什么需要持久化?
-
不想丢掉一些对象
-
内存太贵
-
-
-
mybatis让程序将主要精力放在sql上,通过mybatis提供的映射方式,自由灵活生成(半自动化,大部分需要程序员编写sql)满足需要sql语句。
-
mybatis可以将向 preparedStatement中的输入参数自动进行输入映射,将查询结果集灵活映射成java对象。(输出映射)
Mybatis原理图: *
Mybatis中会将Mapper接口映射Mapper.xml,我们在使用Mybatis时,无需创建Mapper接口具体的实现类,而是利用JDK动态代理,动态的生成接口的实现。 *
3、Mybatis中如何解析所有配置的Mapper映射文件
在mybatis-config.xml中,<mappers>元素的配置方式有以下几种:
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
在<mappers>元素中,有两类子元素<mapper>和<package>,其中<mapper>子元素又有三种属性可以加载对应的配置文件,分别是:resource、url和class。在这几种配置方法中,底层最终都是通过两种方式实现,一种是面向接口编程的方式:通过MapperRegisty的addMapper()方法实现了映射关系的解析和注册,另外一种是解析普通映射配置文件的方式 <mappers>元素解析入口
在XMLConfigBuilder类下的mapperElement方法:
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {//解析<package>元素
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);//通过addMapper()方法解析
} else {//解析<mapper>元素
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
//判断resource是否有值
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();//通过parse()方法解析
} else if (resource == null && url != null && mapperClass == null) {
//判断url是否有值
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();//通过parse()方法解析
} else if (resource == null && url == null && mapperClass != null) {
//判断mapperClass是否有值
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);//通过addMapper()方法解析
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
1、解析<package>元素 该类型的解析,首先是通过属性name获取表示的包名,然后通过反射机制,加载包下所有符合条件的类,然后在通过MapperRegisty的addMapper()方法实现了映射关系的解析和注册。
2、解析属性为resource或url的<mapper>元素 使用<mapper>子元素且使用的属性是resource或url时,他们的解析方式基本上是一样。首先通过资源路径加载资源,然后构建XMLMapperBuilder实例对象,再通过XMLMapperBuilder实例对象的parse()方法来完成解析和注册。
3、解析属性为class的<mapper>元素 使用<mapper>子元素且使用的属性是class时,这种方法和解析<package>元素方法类似,区划就是:一个解析包下的多个类,一个只解析一个类而已,所以仅前置处理不一样。直接通过configuration.addMapper()方法进行解析,而该方法其实就是直接调用了MapperRegisty的addMapper()方法实现了映射关系的解析和注册。
=========================================================================
mapperParser.parse()-->XMLMapperBuilder类下的parse()方法
configuration.addMapper()-->Configuration类下的addMapper()方法
Configuration类下的addMapper()方法-->MapperRegistry下的addMapper方法