一、Mybatis快速入门
- Mybatis介绍:
MyBatis是支持普通SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装。MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数库中的记录。
JDBC->DBUtils->MyBatis->Hibernate->SpringData->SpringJDBC->SpringJPA- Mybatis与Hibernate区别:
- Hibernate是通过对象得到sql语句;封装比较重,不易对sql优化。
- Mybatis是通过sql语句得到对象;轻量级,可以对sql进行优化。
- Mybatis实现原理:
使用java解析xml技术进行解析mybatis.xml,读取jdbc值,进行反射创建数据源。
创建会话工厂,获取到某个会话工厂的连接,通过命名空间+id在配置文件中找到对应sql语句,使用jdbc执行sql语句,解析xml文件的returnType,使用反射为实体类赋值,得到返回值输出。
- Mybatis与Hibernate区别:
- Mybatis环境搭建:
- 添加maven坐标:
<dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.15</version> </dependency> </dependencies>
- 建表:
CREATE TABLE users(id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(20), age INT); INSERT INTO users(NAME, age) VALUES('Tom', 12); INSERT INTO users(NAME, age) VALUES('Jack', 11);
- 添加mybatis配置文件:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver" /> <!-- mysql8以上版本,不加?serverTimezone=GMT,会提示系统时区出现错误 --> <property name="url" value="jdbc:mysql://localhost:3306/architect?serverTimezone=GMT" /> <property name="username" value="root" /> <property name="password" value="root" /> </dataSource> </environment> </environments> </configuration>
- 定义表的实体类:
package chauncy.entity; public class Users { private int id; private String name; private int age; 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; } @Override public String toString() { return "Users [id=" + id + ", name=" + name + ", age=" + age + "]"; } }
- 定义UsersMapper接口:
package chauncy.mapper; import chauncy.entity.Users; public interface UsersMapper { public Users getUsers(int id); }
- 定义操作users表的sql映射文件usersMapper.xml:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="chauncy.mapper.UsersMapper"> <select id="getUsers" parameterType="int" resultType="chauncy.entity.Users"> select * from users where id = #{id} </select> </mapper>
- mybatis.xml文件中加载配置文件:
<mappers> <mapper resource="mybatis/usersMapper.xml"/> </mappers>
- mybatis.xml测试方法:
import java.io.IOException; import java.io.Reader; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import chauncy.entity.Users; public class TestMybatis { public static void main(String[] args) throws IOException { show(); } static public void show() throws IOException{ String resource="mybatis.xml"; //读取配置文件 Reader resourceAsReader = Resources.getResourceAsReader(resource); //创建session会话工厂 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsReader); //获取sql会话 SqlSession openSession = sqlSessionFactory.openSession(); //通过命名空间+id String statement="chauncy.mapper.UsersMapper.getUsers"; Users users = openSession.selectOne(statement,1); System.out.println(users.toString()); openSession.close(); } }
- 添加maven坐标:
- Mybatis的CUD操作实现:
- 增加记录代码实现:
<insert id="addUsers" parameterType="chauncy.entity.Users"> insert into users(name,age) values(#{name},#{age}); </insert>
static public void add() throws IOException{ String resource="mybatis.xml"; //读取配置文件 Reader resourceAsReader = Resources.getResourceAsReader(resource); //创建session会话工厂 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsReader); //获取sql会话 SqlSession openSession = sqlSessionFactory.openSession(); //通过命名空间+id String statement="chauncy.mapper.UsersMapper.addUsers"; Users addUsers = new Users(); addUsers.setName("ChauncyWang"); addUsers.setAge(18); //返回影响行数 int insert = openSession.insert(statement,addUsers); openSession.commit(); System.out.println("insert:"+insert); openSession.close(); }
- 修改记录代码实现:
<update id="updUsers" parameterType="chauncy.entity.Users"> update users set name=#{name},age=#{age} where id = #{id}; </update>
static public void upd() throws IOException{ String resource="mybatis.xml"; //读取配置文件 Reader resourceAsReader = Resources.getResourceAsReader(resource); //创建session会话工厂 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsReader); //获取sql会话 SqlSession openSession = sqlSessionFactory.openSession(); //通过命名空间+id String statement="chauncy.mapper.UsersMapper.updUsers"; Users updUsers = new Users(); updUsers.setId(3); updUsers.setName("chauncy"); updUsers.setAge(20); //返回影响行数 int update = openSession.update(statement,updUsers); openSession.commit(); System.out.println("update:"+update); openSession.close(); }
- 删除记录代码实现:
<delete id="delUsers" parameterType="int"> delete from users where id = #{id}; </delete>
static public void del() throws IOException{ String resource="mybatis.xml"; //读取配置文件 Reader resourceAsReader = Resources.getResourceAsReader(resource); //创建session会话工厂 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsReader); //获取sql会话 SqlSession openSession = sqlSessionFactory.openSession(); //通过命名空间+id String statement="chauncy.mapper.UsersMapper.delUsers"; int delete = openSession.delete(statement,3); openSession.commit(); System.out.println("delete:"+delete); openSession.close(); }
- 增加记录代码实现:
二、SQL注入攻击与防护
- 创建表并添加测试数据:
create table user_table( id int Primary key, username varchar(30), password varchar(30) ); insert into user_table values(1,'ChauncyWang1','123456'); insert into user_table values(2,'ChauncyWang2','123456');
- SQL注入攻击的产生:
通过SQL拼接,传入非法条件(例:’ OR 1=1 – )成立。import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; /** * @classDesc: 功能描述(容易产生SQL注入攻击代码) * @author: ChauncyWang * @version: 1.0 */ public class SqlInjection { public static void main(String[] args) throws SQLException, ClassNotFoundException { String username = "ChauncyWang1"; String password = "123456"; String sql = "SELECT id,username FROM user_table WHERE " + "username='" + username + "'AND " + "password='" + password + "'"; Class.forName("com.mysql.jdbc.Driver"); Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/architect?serverTimezone=GMT", "root", "root"); PreparedStatement stat = con.prepareStatement(sql); System.out.println(stat.toString()); ResultSet rs = stat.executeQuery(); while (rs.next()) { String id = rs.getString(1); String name = rs.getString(2); System.out.println("id:" + id + "---name:" + name); } } }
- SQL注入攻击手法:
将username的值设置为:username=’ ’ OR 1=1 –
因为–表示SQL注释,因此后面语句忽略;
因为1=1恒成立,因此 username=’ ’ OR 1=1 恒成立; - SQL注入攻击的解决办法:
使用?号传参,先编译再执行。import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; /** * @classDesc: 功能描述(SQL注入攻击) * @author: ChauncyWang * @version: 1.0 */ public class SqlInjection { public static void main(String[] args) throws SQLException, ClassNotFoundException { //注入式攻击方法 //String username = "' or 1=1 -- "; String username="' or 1=1 -- "; String password = "123456"; /*//拼接SQL的方式,容易产生SQL注入式攻击 String sql = "SELECT id,username FROM user_table WHERE " + "username='" + username + "'AND " + "password='" + password + "'";*/ //使用?号方式,进行预编译SQL,防止注入式攻击 String sql = "SELECT id,username FROM user_table WHERE username= ? AND password= ?"; Class.forName("com.mysql.jdbc.Driver"); Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/architect?serverTimezone=GMT", "root", "root"); PreparedStatement stat = con.prepareStatement(sql); stat.setString(1, username); stat.setString(2, password); System.out.println(stat.toString()); ResultSet rs = stat.executeQuery(); while (rs.next()) { String id = rs.getString(1); String name = rs.getString(2); System.out.println("id:" + id + "---name:" + name); } } }
- mybatis中#与$区别:
动态 sql 是 mybatis 的主要特性之一,在 mapper 中定义的参数传到 xml 中之后,在查询之前 mybatis 会对其进行动态解析。
mybatis 为我们提供了两种支持动态 sql 的语法:#{} 以及 ${}。
在下面的语句中,如果 username 的值为 zhangsan,则两种方式无任何区别:
select * from user where name = #{name};
select * from user where name = ${name};
其解析之后的结果均为:
select * from user where name = ‘zhangsan’;
但是 #{} 和 ${} 在预编译中的处理是不一样的。#{} 在预处理时,会把参数部分用一个占位符 ? 代替,变成如下的 sql 语句:
select * from user where name = ?;
而 ${} 则只是简单的字符串替换,在动态解析阶段,该 sql 语句会被解析成:
select * from user where name = ‘zhangsan’;
以上,#{} 的参数替换是发生在 DBMS(数据库管理系统)中,而 ${} 则发生在动态解析过程中。
那么,在使用过程中我们应该使用哪种方式呢?
答案是:优先使用 #{},因为 ${} 会导致 sql 注入的问题。
看下面的例子:
select * from ${tableName} where name = #{name}
在这个例子中,如果表名为
user; delete user; –
则动态解析之后 sql 如下:
select * from user; delete user; – where name = ?;
--之后的语句被注释掉,而原本查询用户的语句变成了查询所有用户信息+删除用户表的语句,会对数据库造成重大损伤,极大可能导致服务器宕机。
但是表名用参数传递进来的时候,只能使用 ${} ,具体原因可以自己做个猜测,去验证。这也提醒我们在这种用法中要小心sql注入的问题。
代码实现:- 建表
create table user_table( id int Primary key, username varchar(30), password varchar(30) ); insert into user_table values(1,'ChauncyWang1','123456'); insert into user_table values(2,'ChauncyWang2','123456');
- 定义表的实体类
package chauncy.entity; public class UsersTable { private int id; private String userName; private String passWord; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassWord() { return passWord; } public void setPassWord(String passWord) { this.passWord = passWord; } @Override public String toString() { return "UsersTable [id=" + id + ", userName=" + userName + ", passWord=" + passWord + "]"; } }
- 定义UsersTableMapper接口
package chauncy.mapper; import chauncy.entity.UsersTable; public interface UsersTableMapper { public UsersTable login(UsersTable usersTable); }
- 定义操作usersTable表的sql映射文件usersTableMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="chauncy.mapper.UsersTableMapper"> <select id="login" parameterType="chauncy.entity.UsersTable" resultType="chauncy.entity.UsersTable"> select * from users_table where username=${userName} and password=${passWord} </select> </mapper>
- mybatis.xml文件中加载配置文件
<mappers> <mapper resource="mybatis/usersTableMapper.xml"/> </mappers>
- mybatis.xml测试方法
static public void login() throws IOException{ String resource="mybatis.xml"; //读取配置文件 Reader resourceAsReader = Resources.getResourceAsReader(resource); //创建session会话工厂 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsReader); //获取sql会话 SqlSession openSession = sqlSessionFactory.openSession(); //通过命名空间+id String statement="chauncy.mapper.UsersTableMapper.login"; UsersTable usersTable = new UsersTable(); usersTable.setUserName("'' or 1=1 --"); usersTable.setPassWord("'123456'"); List<UsersTable> usersTableList = openSession.selectList(statement,usersTable); for (UsersTable usersTable2 : usersTableList) { System.out.println(usersTable2.toString()); } openSession.close(); }
- 建表
三、Mybatis 注解使用
只要使用Mybatis3.0以上就可以使用注解,Mybatis提供了CRUD(增删改查)注解:@insert @delete @update @select
- 建立注解Mapper
package chauncy.mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import chauncy.entity.Users; public interface UsersMapper { @Select("select * from users where id=#{id}") public Users getUsers(@Param("id")int id); }
- 加入mybatis.xml
<mapper class="chauncy.mapper.UsersMapper"/>
- 运行测试
public class TestMybatis { public static void main(String[] args) throws IOException { showByAnnocation(); } static public void showByAnnocation() throws IOException{ String resource="mybatis.xml"; //读取配置文件 Reader resourceAsReader = Resources.getResourceAsReader(resource); //创建session会话工厂 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsReader); //获取sql会话 SqlSession openSession = sqlSessionFactory.openSession(); UsersMapper usersMapper = openSession.getMapper(UsersMapper.class); Users users = usersMapper.getUsers(1); System.out.println(users.toString()); openSession.close(); } }
四、Generator使用
Generator逆向生成对应数据库的实体与dao层,Mybatis的逆向工程使用方法有很多,在此不一一列举。