目录
2.2.4 创建Executor接口及实现类SimpleExecutor
3.2 sqlSessionFactory 接口及D efaultSqlSessionFactory 实现类
3.3 sqlSession 接口的实现类 DefaultSqlSession
3.4 Executor接口的实现类SimpleExecutor
4.2 创建user实体类、userdao接口以及mapper.xml
一、分析jdbc
// 加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
// 通过驱动管理类获取数据库链接
Connection connection =
DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?
characterEncoding=utf-8", "root", "root");
// 定义sql语句?表示占位符
String sql = "select * from user where username = ?";
// 获取预处理statement
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
preparedStatement.setString(1, "tom");
// 向数据库发出sql执行查询,查询出结果集
ResultSet resultSet = preparedStatement.executeQuery();
// 遍历查询结果集
while (resultSet.next()) {
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
// 封装User
user.setId(id);
user.setUsername(username);
}
原始jdbc开发存在的问题如下:
- 数据库连接创建、释放频繁造成系统资源浪费,从而影响系统性能。
- Sql语句在代码中硬编码,造成代码不易维护,实际应用中sql变化的可能较大,sql变动需要改变java代码。
- 使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
- 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据 库记录封装成pojo对象解析比较方便.
问题解决思路:
- 使用数据库连接池初始化连接资源
- 将sql语句抽取到xml配置文件中
- 使用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射
二、自定义框架设计
2.1 使用端
提供核心配置文件:
- sqlMapConfig.xml : 存放数据源信息,引入mapper.xml
- Mapper.xml : sql语句的配置文件信息
编写代码 :
- pojo类
- 针对个Mapper.xml生成Dao类
2.2 框架端
2.2.1 读取配置文件
读取配置文件并可以创建javaBean来存储:
- Configuration : 存放数据库基本信息(读取sqlMapConfig.xml)、以及Map<唯一标识,Mapper> MapperStatements存储读取Mapper.xml的MapperStatement信息。其中唯一标识=namespace(mapper级别) + "." +id(每一条sql id)。
- MapperStatement:是mapper.xml中的每一条sql信息,包含了sql语句、statement类型、输入参数java类型、输出参数java类型
2.2.2 解析配置文件
创建sqlSessionFactoryBuilder类,包含构建sqlSessionFactory的build()方法:
- 第一步:使用dom4j解析配置文件,将解析出来的内容封装到Configuration和MappedStatement中
- 第二步:创建SqlSessionFactory的实现类DefaultSqlSession
2.2.3 创建sqlSession接口及实现类
- 调用sqlSessionFactory的openSession()方法,得到SqlSession对象
- SqlSession中提供增删改查方法,以及getMapper方法(主要通过反射形式获取到要执行的mapperdao,然后调用对应的MapperStatement)
- SqlSession实现类DefaultSqlSession 实现类中调用excutor接口真正实现sql的执行
2.2.4 创建Executor接口及实现类SimpleExecutor
在方法中进行真正增删该查的执行。
三、框架端主要部分代码实现
3.1 读取配置文件
/**
* 该方法就是使用dom4j对配置文件进行解析,封装Configuration
*/
public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
Document document = new SAXReader().read(inputStream);
//<configuration>
Element rootElement = document.getRootElement();
List<Element> list = rootElement.selectNodes("//property");
Properties properties = new Properties();
for (Element element : list) {
String name = element.attributeValue("name");
String value = element.attributeValue("value");
properties.setProperty(name,value);
}
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
comboPooledDataSource.setUser(properties.getProperty("username"));
comboPooledDataSource.setPassword(properties.getProperty("password"));
configuration.setDataSource(comboPooledDataSource);
//mapper.xml解析: 拿到路径--字节输入流---dom4j进行解析
List<Element> mapperList = rootElement.selectNodes("//mapper");
for (Element element : mapperList) {
String mapperPath = element.attributeValue("resource");
InputStream resourceAsSteam = Resources.getResourceAsSteam(mapperPath);
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
xmlMapperBuilder.parse(resourceAsSteam);
}
return configuration;
}
//此方法主要对mapper.xml的解析
public void parse(InputStream inputStream) throws DocumentException {
Document document = new SAXReader().read(inputStream);
Element rootElement = document.getRootElement();
String namespace = rootElement.attributeValue("namespace");
List<Element> list = rootElement.selectNodes("//select");
for (Element element : list) {
String id = element.attributeValue("id");
String resultType = element.attributeValue("resultType");
String paramterType = element.attributeValue("paramterType");
String sqlText = element.getTextTrim();
MappedStatement mappedStatement = new MappedStatement();
mappedStatement.setId(id);
mappedStatement.setResultType(resultType);
mappedStatement.setParamterType(paramterType);
mappedStatement.setSql(sqlText);
String key = namespace+"."+id;
configuration.getMappedStatementMap().put(key,mappedStatement);
}
}
3.2 sqlSessionFactory 接口及D efaultSqlSessionFactory 实现类
public interface SqlSessionFactory {
public SqlSession openSession();
}
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
public SqlSession openSession(){
return new DefaultSqlSession(configuration);
}
}
3.3 sqlSession 接口的实现类 DefaultSqlSession
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}
@Override
public <E> List<E> selectList(String statementid, Object... params) throws Exception {
//将要去完成对simpleExecutor里的query方法的调用
SimpleExecutor simpleExecutor = new SimpleExecutor();
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementid);
List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
return (List<E>) list;
}
@Override
public <T> T getMapper(Class<?> mapperClass) {
// 使用JDK动态代理来为Dao接口生成代理对象,并返回
Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 底层都还是去执行JDBC代码 //根据不同情况,来调用selctList或者selectOne
// 准备参数 1:statmentid :sql语句的唯一标识:namespace.id= 接口全限定名.方法名
// 方法名:findAll
String methodName = method.getName();
String className = method.getDeclaringClass().getName();
String statementId = className+"."+methodName;
if(methodName.contains("insert")){
int result = insert(statementId, args);
return result;
}
if(methodName.contains("update")){
int result = update(statementId, args);
return result;
}
if(methodName.contains("delete")){
int result = delete(statementId, args);
return result;
}
// 准备参数2:params:args
// 获取被调用方法的返回值类型
Type genericReturnType = method.getGenericReturnType();
// 判断是否进行了 泛型类型参数化
if(genericReturnType instanceof ParameterizedType){
List<Object> objects = selectList(statementId, args);
return objects;
}
return selectOne(statementId,args);
}
});
return (T) proxyInstance;
}
}
3.4 Executor接口的实现类SimpleExecutor
public class SimpleExecutor implements Executor {
@Override
public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
// 1. 注册驱动,获取连接
Connection connection = configuration.getDataSource().getConnection();
// 2. 获取sql语句 : select * from user where id = #{id} and username = #{username}
//转换sql语句: select * from user where id = ? and username = ? ,转换的过程中,还需要对#{}里面的值进行解析存储
String sql = mappedStatement.getSql();
BoundSql boundSql = getBoundSql(sql);
// 3.获取预处理对象:preparedStatement
PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
// 4. 设置参数
//获取到了参数的全路径
String paramterType = mappedStatement.getParamterType();
Class<?> paramtertypeClass = getClassType(paramterType);
List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
for (int i = 0; i < parameterMappingList.size(); i++) {
ParameterMapping parameterMapping = parameterMappingList.get(i);
String content = parameterMapping.getContent();
//反射
Field declaredField = paramtertypeClass.getDeclaredField(content);
//暴力访问
declaredField.setAccessible(true);
Object o = declaredField.get(params[0]);
preparedStatement.setObject(i+1,o);
}
// 5. 执行sql
ResultSet resultSet = preparedStatement.executeQuery();
String resultType = mappedStatement.getResultType();
Class<?> resultTypeClass = getClassType(resultType);
ArrayList<Object> objects = new ArrayList<>();
// 6. 封装返回结果集
while (resultSet.next()){
Object o =resultTypeClass.newInstance();
//元数据
ResultSetMetaData metaData = resultSet.getMetaData();
for (int i = 1; i <= metaData.getColumnCount(); i++) {
// 字段名
String columnName = metaData.getColumnName(i);
// 字段的值
Object value = resultSet.getObject(columnName);
//使用反射或者内省,根据数据库表和实体的对应关系,完成封装
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
Method writeMethod = propertyDescriptor.getWriteMethod();
writeMethod.invoke(o,value);
}
objects.add(o);
}
return (List<E>) objects;
}
//省略增删改的实现代码
}
四、客户端调用主要代码
4.1 sqlMapConfig.xml
〈configuration〉
<!--数据库连接信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///zdy_mybatis"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
<! --引入sql配置信息-->
<mapper resource="mapper.xml"></mapper>
</configuration>
4.2 创建user实体类、userdao接口以及mapper.xml
public class User {
//主键标识
private Integer id;
//用户名
private String username;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
public interface IUserDao {
//查询所有用户
public List<User> findAll() throws Exception;
//根据条件进行用户查询
public User findByCondition(User user) throws Exception;
//根据条件进行用户查询
public Integer insertUser(User user) throws Exception;
//根据条件进行用户查询
public Integer updateUser(User user) throws Exception;
//根据条件进行用户查询
public Integer deleteUser(User use) throws Exception;
}
<mapper namespace="com.lagou.dao.IUserDao">
<!--sql的唯一标识:namespace.id来组成 : statementId-->
<select id="findAll" resultType="com.lagou.pojo.User" >
select * from user
</select>
<select id="findByCondition" resultType="com.lagou.pojo.User" paramterType="com.lagou.pojo.User">
select * from user where id = #{id} and username = #{username}
</select>
<select id="insertUser" resultType="java.lang.Integer" paramterType="com.lagou.pojo.User">
insert into user(id, username) values(#{id}, #{username})
</select>
<select id="updateUser" resultType="java.lang.Integer" paramterType="com.lagou.pojo.User">
update user set username=#{username} where id=#{id}
</select>
<select id="deleteUser" resultType="java.lang.Integer" paramterType="com.lagou.pojo.User">
delete from user where id=#{id}
</select>
</mapper>
4.3 进行测试类的编写
public class IPersistenceTest {
@Test
public void test() throws Exception {
InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
SqlSession sqlSession = sqlSessionFactory.openSession();
//调用
User user = new User();
user.setId(1);
user.setUsername("张三");
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
List<User> all = userDao.findAll();
for (User user1 : all) {
System.out.println(user1);
}
}
@Test
public void testInsert() throws Exception {
InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
SqlSession sqlSession = sqlSessionFactory.openSession();
//调用
User user = new User();
user.setId(3);
user.setUsername("mike");
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
userDao.insertUser(user);
}
@Test
public void testUpdate() throws Exception {
InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
SqlSession sqlSession = sqlSessionFactory.openSession();
//调用
User user = new User();
user.setId(3);
user.setUsername("nike");
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
userDao.updateUser(user);
}
@Test
public void testDelete() throws Exception {
InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
SqlSession sqlSession = sqlSessionFactory.openSession();
//调用
User user = new User();
user.setId(3);
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
userDao.deleteUser(user);
}
}