1.导入依赖
dom4j和jaxen用于解析xml,junit用于搭建测试环境,mysql用于配置数据源,得到连接对象
<dependencies>
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
2.创建SqlSessionFactoryBuild类
该类的作用:通过build方法返回SqlSessionFactory对象
public SqlSessionFactory build(InputStream in){
//事务对象
Transaction transaction=null;
//Map集合,key是sqlId, value是封装了SQL标签(sql语句和查询返回类型)的MapperStatement对象
Map<String,MapperStatement> mapperStatementMap=new HashMap<>();
SqlSessionFactory sqlSessionFactory = new SqlSessionFactory(transaction,mapperStatementMap);
return sqlSessionFactory;
}
3.创建SqlSessionFactory类
该类的作用:通过openSession方法返回SqlSession对象
public class SqlSessionFactory {
public SqlSession openSession(){
SqlSession sqlSession = new SqlSession(this);sm
return sqlSession;
}
}
类中需要两个属性:事务属性
和Map属性
public class SqlSessionFactory {
//1.需要事务属性(里面有dataSource属性)
Transaction transaction;
//2.需要Map集合属性(key:sqlid, value: sql标签对象)
Map<String,MapperStatement> mapperStatementMap;
public SqlSessionFactory(Transaction transaction, Map<String,MapperStatement> mapperStatementMap){
this.transaction=transaction;
this.mapperStatementMap=mapperStatementMap;
}
}
分析SqlSessionFactory类需要什么属性
要明确这个问题,我们要先从MyBatis框架的用户使用流程来倒推.用户使用MyBatis,第一步就是编写全局配置文件
mybatis-config.xml
和userMpper.xml
,mybatis-config.xml中主要包含事务和数据源两方面的配置,userMpper.xml包含select语句的标签,创建SqlSessionFactory对象的时候需要解析配置文件的内容,封装成对象传到SqlSessionFactory对象中.从两个配置文件可看出,要得到SqlSessionFactory对象,至少需要事务,数据源和sql标签等三方面数据.由此我们可以得出结论:创建SqlSessionFactory对象需要事务属性,数据源属性和sql标签属性.
数据源属性
作用:数据源的作用就是创建连接对象Connection
属性: 需要 url
username
password
driver
private String url;
private String username;
private String password;
private Driver driver;
对象获取方式:实现javax.sql.DataSource接口(jdk的接口)
public class Unpooled implements DataSource {
private String url;
private String username;
private String password;
private Driver driver;
public Unpooled(String driver,String url,String username,String password){
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
this.url=url;
this.username=username;
this.password=password;
}
@Override
public Connection getConnection() throws SQLException {//每次都是创建新的连接对象
return DriverManager.getConnection(url,username,password);
}
}
常见的数据源对象(均实现了DataSource对象)
Unpooled: 不用数据库连接池,每一次都是创建新的连接对象(此番手写框架只实现这个类)
Pooled : 使用MyBatis实现的数据库连接池获取连接对象(不实现)
JNDI : 使用第三方的数据库连接池获取连接对象(不实现)
事务属性
作用:提供管理事务的相关方法,如commit,rollback,close等方法
对象获取方式:自定义事务接口Transcation,实现Transacton接口获取对应事务类
定义Transaction接口
public interface Transaction {//有JDBC和MANAGED两种事务选择,分别对应两个实现类
void commit();
void rollback();
void close();
Connection getConnection() throws SQLException;
}
事务实现类
public class JdbcTransaction implements Transaction{//调用JDBC的事务实现
//需要Connection对象
private DataSource dataSource;
private Connection connection;
public JdbcTransaction(DataSource dataSource){
this.dataSource=dataSource;
}
public Connection getConnection() throws SQLException {
if (connection == null) {
connection=dataSource.getConnection();
}
return connection;
}
@Override
public void commit() {
try {
connection.commit();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
//调用JDBC的Connection对象的commit方法
}
@Override
public void rollback() {
try {
connection.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
//调用JDBC的Connection对象的rollback方法
}
@Override
public void close() {
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
//调用JDBC的Connection对象的close方法
}
}
常见事务实现类
JDBC: 通过借助jdbc的Connection对象调用commit,rollback,close等方法实现事务(本次框架实习这个类)
MANAGED: 通过第三方实现事务,一般而言通过Spring容器实现事务.(未实现)
观察事务实现类中可得知,事务的commit,rollback,close方法都是通过Connection对象来调用的,由此我们得知,事务对象中一定要有Connection对象,而要获取Connection对象,就必须要DataSource对象,要DataSource对象就必须从配置文件中获取到Drive,url,username,password等信息,同时由于事务对象中包含数据源对象,那么SqlSessionFactory中有事务属性就能获取到数据源属性.由此SqlSessionFactory中只需要事务属性和Sql标签属性.
深入Map属性(mapperStatementMap)
作用:封装存放所有的bean配置类.
为什么需要此类:在获取到连接对象后,我们要先获取sql语句,执行sql,返回sql处理结果,因此我们需要一个属性能得到sql语句,返回类.此时我们在返回配置文件,主配置文件只有一个,但是每个javabean类都有一个userMpper.xml的配置文件,里面编写对应bean的增删改查的sql语句和查询得到的结果集的返回值类型.由于每个bean的配置类都会加载到主配置类.我们考虑新建一个MapperStatement类,这类里面封装了sql语句和返回值类型,同时用一个Map集合存放所有的bean配置类,key是配置类的namespace+id的组合字符串,value就是MapperStatement对象.
MapperStatement类
public class MapperStatement {
private String sql;
private String resultType;
}
Map<String,MapperStatement> mapperStatementMap
for (Element e :elements) {
String id = e.attributeValue("id");
String resultType = e.attributeValue("resultType");
String sql = e.getTextTrim();
String sqlId=namespace+"."+id;
MapperStatement mapperStatement = new MapperStatement(sql, resultType);
mapperStatementMap.put(sqlId,mapperStatement);
}
解析配置文件
//解析两个配置文件获取配置文件中的信息,并封装为Transaction和map
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(in);
Element environments = (Element) document.selectSingleNode("/configuration/environments");
String defaultId = environments.attributeValue("default");//获取default的id值
//获取与default的id一致的environment
Element environElt=(Element) environments.selectSingleNode("/configuration/environments/environment[@id='"+defaultId+"']");
Element transactionManagerElt = environElt.element("transactionManager");
//事务类型
String transcationType = transactionManagerElt.attributeValue("type").trim().toUpperCase();
Element dataSourceElt = environElt.element("dataSource");
//数据源类型及数据源4个数据
String dataSourceType = dataSourceElt.attributeValue("type");
List<Element> propertys = dataSourceElt.elements("property");
Map<String ,String> dataMap=new HashMap<>();
//把driver,url,username,password属性封装到map中
for(Element e:propertys){
String name = e.attributeValue("name");
String value = e.attributeValue("value");
dataMap.put(name,value);
}
//判断用户配置的数据源(UNPOOLED,POOLED,JNDI)
if (Const.DATASOURCE_UNPOOLED.equals(dataSourceType)) {
dataSource=new Unpooled(dataMap.get("driver"),dataMap.get("url"),dataMap.get("username"),dataMap.get("password"));
}
if (Const.DATASOURCE_POOLED.equals(dataSourceType)) {
dataSource=new Pooled(dataMap.get("driver"),dataMap.get("url"),dataMap.get("username"),dataMap.get("password"));
}
if (Const.DATASOURCE_JNDI.equals(dataSourceType)) {
dataSource=new JNDI(dataMap.get("driver"),dataMap.get("url"),dataMap.get("username"),dataMap.get("password"));
}
//判断用户配置的事务类型(JDBC,MANAGED)
if (Const.TRANSACTION_JDBC.equals(transcationType)) {
transaction=new JdbcTransaction(dataSource);
}
if (Const.TRANSACTION_MANAGED.equals(transcationType)) {
transaction=new Managed(dataSource);
}
//封装mapperStatementMap
List<Node> nodes = document.selectNodes("//mapper");
for (Node node: nodes) {
String resource = ((Element) node).attributeValue("resource");
InputStream mapper = Resource.getResourceAsStream(resource);
SAXReader saxReader1=new SAXReader();
Document mapperDocument = saxReader1.read(mapper);
Element mapperElt= (Element) mapperDocument.selectSingleNode("/mapper");
String namespace = mapperElt.attributeValue("namespace");
List<Element> elements = mapperElt.elements();
for (Element e :elements) {
String id = e.attributeValue("id");
String resultType = e.attributeValue("resultType");
String sql = e.getTextTrim();
String sqlId=namespace+"."+id;
MapperStatement mapperStatement = new MapperStatement(sql, resultType);
mapperStatementMap.put(sqlId,mapperStatement);
}
}
4.创建SqlSession对象
通过前期的努力,我们拿到了数据源对象,事务对象,map集合,有了这些,我们成功创建了SqlSessionFactory对象,通过该对象的openSession对象得到了SqlSession对象.而有了SqlSession对象我们就可以调用crud方法来实现数据库操作.
实现insert和select方法
-
insert(sqlId,bean)
- sql中占位符需要用正则替换:#\{[a-zA-Z0-9_$]*}
- 给?传值
- 不知道有多少个?
- 先获取到第一个井号的索引
- 在获取}的索引
- 然后substring进行裁串
- 用一个变量来记录#个数,位置
- 不知道要给bean的哪个属性赋值给哪个?
- 有属性的名称,通过反射调用bean的get方法名称
- 方法名:get+properName.toUpperCase().chartAt(0)+por.subString(1)
- Obj obj=pojo.getClass().getDeclaredMethod(getMethodName).invoke(bean)
- ps.setString(index,obj)
- 有属性的名称,通过反射调用bean的get方法名称
- 都用ps.setString(),数据库中都是varchar
- 不知道有多少个?
public int insert(String sqlId,Object obj){//插入 Connection connection=null; int count=0; try { connection = this.sqlSessionFactory.transaction.getConnection(); MapperStatement mapperStatement = this.sqlSessionFactory.mapperStatementMap.get(sqlId); //insert inot User(id,name,age) values(#{id},#{name},#{age}) String sql = mapperStatement.getSql(); //insert into User(id,name,age) values(?,?,?); String newSql = sql.replaceAll("#\\{[A-Za-z0-9_$]*}", "?"); PreparedStatement ps = connection.prepareStatement(newSql); //现在要做的事情,就是得到有多少个参数需要替换,每个参数如何调用对应Bean的get方法从bean中取值,并把值替换到sql中 int index=0; while(true){ int jingHaoIndex = sql.indexOf("#");//第一个出现的井号下标 if (jingHaoIndex <0 ) {//说明没有#号了 break; } int youkuohaoIndex = sql.indexOf("}");//第一个右括号下标 String param = sql.substring(jingHaoIndex + 2, youkuohaoIndex).trim();//获取到的第一个参数名 String setMethodName="get"+param.toUpperCase().charAt(0)+param.substring(1);//得到参数对应的get方法名称:getId Class<?> o = obj.getClass(); Method declaredMethod = o.getDeclaredMethod(setMethodName); Object invoke = declaredMethod.invoke(obj); index++; sql=sql.substring(youkuohaoIndex+1); ps.setString(index,invoke.toString()); } count = ps.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } return count; }
-
select
- Class.forName(String resultType):获取结果集对象返回的类
- 得到Obj对象: resultClass.newInstance(),无参构造,属性为空
- 将查询结构的数据库的列名当作属性名:rs.getMetaData() 列名集合
- 列名.getColumnCount得到有多少列
- rsmd.gerColumnName(i):得到第i列的列名称
- commit,rollabck,close:调用factory的commit,rollback,close方法
public Object selectOne(String sqlId,String id){
Connection connection=null;
try {
connection=this.sqlSessionFactory.transaction.getConnection();
// select * from user where id= #{id}
String sql = this.sqlSessionFactory.mapperStatementMap.get(sqlId).getSql();
// select * from user where id= ?
String newSql = sql.replaceAll("#\\{[a-zA-Z0-9_$]*}", "?");
String resultType = this.sqlSessionFactory.mapperStatementMap.get(sqlId).getResultType();
PreparedStatement preparedStatement = connection.prepareStatement(newSql);
preparedStatement.setString(1,id);
ResultSet resultSet = preparedStatement.executeQuery();
Class<?> resultClass = Class.forName(resultType);
Object o = resultClass.newInstance();
if (resultSet.next()) {
ResultSetMetaData metaData = resultSet.getMetaData();//获取数据库表中的所所有字段集合
for (int i = 0; i < metaData.getColumnCount(); i++) {
String columnName = metaData.getColumnName(i+1);//遍历得到每个字段名称
String setMethod="set"+columnName.toUpperCase().charAt(0)+ columnName.substring(1);//得到每个字段的set方法名称
Method declaredMethod = o.getClass().getDeclaredMethod(setMethod,String.class);//得到set方法
declaredMethod.invoke(o, resultSet.getString(columnName));//调用set方法
}
}
return o;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
评论区