如果我们没有mybatis下,怎么用java语言去实现,学一个东西得明白这个东西干嘛用,解决什么问题,有结果推向过程,最后才能更好的明白过程。
1、mybatis干嘛用?解决了什么痛点?
MyBatis是一流的持久性框架,支持自定义SQL,存储过程和高级映射。MyBatis消除了几乎所有的JDBC代码以及参数的手动设置和结果检索。MyBatis可以使用简单的XML或注释进行配置,并将图元,映射接口和Java POJO(普通的旧Java对象)映射到数据库记录。
mybatis是一个orm框架,也就是把字段映射为对象的属性。
2、首先复习下java的jdbc
JDBC(Java DataBase Connectivity)是Java和数据库之间的一个桥梁,是一个规范而不是一个实现,能够执行SQL语句。它由一组用Java语言编写的类和接口组成。各种不同类型的数据库都有相应的实现,本文中的代码都是针对MySQL数据库实现的。
JDBC编程步骤
(1)、导入专用的jar包(不同的数据库需要的jar包不同)
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.39</version>
</dependency>
(2)、初始化驱动
Class.forName("com.mysql.jdbc.Driver");
(3)、建立JDBC和数据库之间的Connection连接
String url = "jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8";
String name = "root";
String password = "123456";
// 获取数据库连接
Connection connection = DriverManager.getConnection(url, name, password);
(4)、创建Statement或者PreparedStatement接口,执行SQL语句
使用Statement接口
Statement中使用字符串拼接的方式,该方式存在句法复杂,容易犯错等缺点,且存在sql注入风险
Statement s = conn.createStatement();
// 准备sql语句
// 注意: 字符串要用单引号'
String sql = "insert into t_courses values(null,"+"'数学')";
//在statement中使用字符串拼接的方式,这种方式存在诸多问题
s.execute(sql);
System.out.println("执行插入语句成功");
使用PreparedStatement接口
PreparedStatement也是用来执行sql语句的与创建Statement不同的是,需要根据sql语句创建PreparedStatement。除此之外,还能够通过设置参数,指定相应的值,而不是Statement那样使用字符串拼接。
String sql = "select * from user where user_name=?";
// 创建Statement对象(每一个Statement为一次数据库执行请求)
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 设置传入参数
preparedStatement.setString(1, "zhangsan");
(5)、执行SQL语句
resultSet = preparedStatement.executeQuery();
(6)、处理查询结果(将查询结果转换成List
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
while (resultSet.next()) {
Map map = new HashMap();
for (int i = 0; i < columnCount; i++) {
String columnName = metaData.getColumnName(i + 1);
map.put(columnName, resultSet.getString(columnName));
}
resultList.add(map);
}
(7)、释放资源
/**
* 封装三个关闭方法
* @param pstmt
*/
public static void close(PreparedStatement pstmt){
if(pstmt != null){ //避免出现空指针异常
try{
pstmt.close();
}catch(SQLException e){
e.printStackTrace();
}
}
}
public static void close(Connection conn){
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
public static void close(ResultSet rs){
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
完整代码
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class JdbcTest {
public static void main(String[] args) {
List<Map<String, Object>> user = getUser();
System.out.println(user);
}
private static List<Map<String, Object>> getUser() {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
List<Map<String, Object>> resultList = new ArrayList<Map<String, Object>>();
try {
// 加载JDBC驱动
Class.forName("com.mysql.jdbc.Driver").newInstance();
String url = "jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8";
String name = "root";
String password = "123456";
// 获取数据库连接
connection = DriverManager.getConnection(url, name, password);
String sql = "select * from user where user_name=?";
// 创建Statement对象(每一个Statement为一次数据库执行请求)
preparedStatement = connection.prepareStatement(sql);
// 设置传入参数
preparedStatement.setString(1, "zhangsan");
// 执行SQL语句
resultSet = preparedStatement.executeQuery();
// 处理查询结果(将查询结果转换成List<Map>格式)
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
while (resultSet.next()) {
Map map = new HashMap();
for (int i = 0; i < columnCount; i++) {
String columnName = metaData.getColumnName(i + 1);
map.put(columnName, resultSet.getString(columnName));
}
resultList.add(map);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
// 关闭结果集
if (resultSet != null) {
resultSet.close();
resultSet = null;
}
// 关闭执行
if (preparedStatement != null) {
preparedStatement.close();
preparedStatement = null;
}
if (connection != null) {
connection.close();
connection = null;
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return resultList;
}
}
3、mybatis
mybatis的基本构成
1)、SqlSessionFactoryBuilder(构造器):根据配置信息或代码来生成SqlSessionFactory
2)、SqlSessionFactory:依靠工厂来生成SqlSession(会话)
3)、SqlSession:发送SQL去执行并返回结果,也可以获取mapper的接口(Executor 才是真正执行sql)。
4)、 Mapper:它是由一个Java接口和XML文件(注解)构成的,需要给出对应的SQL和映射规则。它负责发送sql去执行,并返回结果。
mybatis的基本构成的生命周期:
1)、SqlSessionFactoryBuilder:在方法内部有效,只用于生成SqlSessionFactory
2)、SqlSessionFactory:用于生成SqlSession(会话)整个mybatis应用生命周期(单例)
3)、SqlSession:相当于一个Connection,一个请求数据库的
4)、Mapper:是一个接口,发送sql返回需要的值,最大生命跟SqlSession范围一样大。
(1)、导jar包
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
(2)、测试和应用
mysql表语句
/*
Navicat MySQL Data Transfer
Source Server : 127.0.0.1
Source Server Version : 50640
Source Host : localhost:3306
Source Database : test
Target Server Type : MYSQL
Target Server Version : 50640
File Encoding : 65001
Date: 2020-06-03 00:21:16
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(32) NOT NULL AUTO_INCREMENT,
`userName` varchar(32) NOT NULL,
`passWord` varchar(50) NOT NULL,
`realName` varchar(32) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'aa', '11', '11');
实体类
import java.io.Serializable;
import java.sql.Date;
public class User implements Serializable {
private long id;
private String userName;
private String passWord;
private Date createTime;
private String name;
public long getId() {
return id;
}
public void setId(long 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;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", userName='" + userName + '\'' +
", passWord='" + passWord + '\'' +
", createTime=" + createTime +
", name='" + name + '\'' +
'}';
}
}
XML文件构建SqlSessionFactory实现CURD
每个MyBatis应用程序都以SqlSessionFactory实例为中心。可以使用SqlSessionFactoryBuilder获取SqlSessionFactory实例。SqlSessionFactoryBuilder可以从XML配置文件或Configuration类的自定义准备好的实例中构建SqlSessionFactory实例。
从XML文件构建SqlSessionFactory实例非常简单。建议您为该配置使用类路径资源,但可以使用任何InputStream实例,包括从文字文件路径或file:// URL创建的实例。MyBatis包含一个称为资源的实用程序类,该实用程序类包含许多方法,这些方法使从类路径和其他位置加载资源更加容易。
mapper文件
import pojo.User;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserMapper {
List<User> selectUserList();
}
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
XML简单配置文件
<?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>
<properties resource="db.properties"></properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserMapper.xml"></mapper>
</mappers>
</configuration>
基于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="dao.UserMapper">
<select id="selectUserList" resultType="pojo.User">
select * from user
</select>
</mapper>
SqlSession调用的基于XML的映射语句
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.selectUserList();
for (User user : users) {
System.out.println(user);
}
不使用XML的情况下构建SqlSessionFactory实现CURD
映射语句根本不需要与XML映射。相反,他们可以使用Java注释。
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import pojo.User;
import java.util.List;
@Mapper
public interface UserMapper {
@Select("select * from user")
List<User> selectUserList();
}
public static void main(String[] args) {
SqlSession sqlSession = null;
try {
sqlSession = getSqlSessionFactory().openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.selectUserList();
for (User user : users) {
System.out.println(user);
}
} catch (Exception e) {
System.out.println("执行失败");
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
private static SqlSessionFactory getSqlSessionFactory() {
//创建线程池
PooledDataSource dataSource = new PooledDataSource();
dataSource.setDriver("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8");
dataSource.setUsername("root");
dataSource.setPassword("123456");
//构建数据库事物
TransactionFactory transactionFactory = new JdbcTransactionFactory();
//构建数据库✅环境
Environment evironment = new Environment("development", transactionFactory, dataSource);
//构建Configuration对象
Configuration configuration = new Configuration(evironment);
//注册一个别名
configuration.getTypeAliasRegistry().registerAlias("user", User.class);
//添加映射器
configuration.addMapper(UserMapper.class);
/**
* 以上是Mybatis配置信息的处理过程正常开发中主要通过xml配置的
* 以下是创建sqlSessionFactory
*/
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
return sqlSessionFactory;
}
动手模拟一次Mybatis的动态代理
代码实例:
import dao.UserMapper;
import org.apache.ibatis.annotations.Select;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashMap;
public class ProxyMapperTest {
public static void main(String[] args) {
UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(ProxyMapperTest.class.getClassLoader(), new Class<?>[]{UserMapper.class}, new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("被动态代理类回调执行, 代理类 proxyClass =" + proxy.getClass()
+ " 方法名: " + method.getName() + "方法. 方法返回类型:" + method.getReturnType()
+ " 接口方法入参数组: " + (args == null ? "null" : Arrays.toString(args)));
Select annotation = method.getAnnotation(Select.class);
// 赋值到#
Parameter[] parameters = method.getParameters();
HashMap map = getParametersMap(parameters, args);
if (annotation != null) {
String[] value = annotation.value();
String sql = value[0];
String sqlStr = getSql(sql, map);
System.out.println(sqlStr);
}
return null;
}
});
userMapper.selectUserById(1);
}
private static String getSql(String sql, HashMap map) {
StringBuilder sb = new StringBuilder();
int length = sql.length();
for (int i = 0; i < length; i++) {
char a = sql.charAt(i);
if (a == '#') {
int nextIndex = i + 1;
if ('{' != sql.charAt(nextIndex)) {
throw new RuntimeException(String.format("这里应该为{%s,index%s", sb.toString(), nextIndex));
}
StringBuilder sbs = new StringBuilder();
i = parSqlarg(sbs, sql, nextIndex);
Object argValue = map.get(sbs.toString());
sb.append(argValue);
continue;
}
sb.append(a);
}
return sb.toString();
}
private static int parSqlarg(StringBuilder sbs, String sql, int nextIndex) {
nextIndex++;
for (int i = nextIndex; i < sql.length(); i++) {
char b = sql.charAt(nextIndex++);
if ('}' != b) {
sbs.append(b);
continue;
}
if ('}' == b) {
return nextIndex;
}
}
throw new RuntimeException(String.format("这里应该为}%s,index%s", sbs.toString(), nextIndex));
}
private static HashMap getParametersMap(Parameter[] parameters, Object[] args) {
HashMap map = new HashMap();
int index[] = {0};
Arrays.asList(parameters).forEach(parameter -> {
String name = parameter.getName();
map.put(name, args[index[0]]);
index[0]++;
});
System.out.println(Arrays.toString(parameters));
return map;
}
}
jdk1.8提供了获取参数名的方法
但是在编译的时候要加上–parameters参数,如果不加这个参数会得到参数名为arg0…
Parameter[] parameters = methods.getParameters();
System.out.println(parameters[0].getName());
但是如果使用maven,只需要在编译插件上加上一个配置
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgument>-parameters</compilerArgument>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
果使用IDEA打开javac设置