您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息

怎么用Java手写持久层框架

2025/2/5 19:56:33发布34次查看
jdbc操作回顾及问题分析学习java的同学一定避免不了接触过jdbc,让我们来回顾下初学时期接触的jdbc操作吧
以下代码连接数据库查询用户表信息,用户表字段分别为用户id,用户名username。
connection connection = null; preparedstatement preparedstatement = null; resultset resultset = null; user user = new user(); try { // 加载数据库驱动 //class.forname("com.mysql.jdbc.driver"); class.forname("com.mysql.cj.jdbc.driver"); // 通过驱动管理类获取数据库链接 connection = drivermanager.getconnection("jdbc:mysql://localhost:3306/mybatis?characterencoding=utf-8", "root", "mimashi3124"); // 定义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()) { int id = resultset.getint("id"); string username = resultset.getstring("username"); // 封装user user.setid(id); user.setusername(username); } system.out.println(user); } catch ( exception e) { e.printstacktrace(); } finally { // 释放资源 if (resultset != null) { try { resultset.close(); } catch (sqlexception e) { e.printstacktrace(); } } if (preparedstatement != null) { try { preparedstatement.close(); } catch (sqlexception e) { e.printstacktrace(); } } if (connection != null) { try { connection.close(); } catch (sqlexception e) { e.printstacktrace(); } } }
查看代码我们可以发现使用jdbc操作数据库存在以下问题:
数据库连接创建、释放频繁造成系统资源浪费,从⽽影响系统性能。
sql语句我们是写在代码里的,代码不容易维护,实际应⽤中sql变化的可能较⼤,sql变动需要改变 java代码。
使⽤preparedstatement向占有位符号传参数存在硬编码,因为sql语句的where条件不⼀定,可能多也可能少,修改sql还要修改代码,系统不易维护。
对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据 库记录封装成pojo对象解析⽐较⽅便
问题解决思路
使⽤数据库连接池初始化连接资源,避免资源浪费
将sql语句抽取到xml配置中,这种sql的变动只用关注xml文件,不比去一堆java代码里改写sql
参数硬编码问题可以使用反射、内省等技术、自动将实体与表字段进行映射。
自己动手写个持久层框架
接下来,我们来一个个解决上面的问题
数据库连接池我们可以直接使用c3p0提供的combopooleddatasource即可
为了解决sql硬编码问题,我们要把sql写到xml文件中,那自然是要定义一个xml文件了。
光有sql肯定不行,毕竟我们要先连接数据库,sql语句才有存在的意义。所以xml中得先定义数据配置信息,然后才是sql语句。
1.定义配置xml文件我们新建一个sqlmapconfig.xml,定义数据源信息、并且增加两个sql语句,parametertype为sql执行参数,resulttype为方法返回实体。
代码如下(数据库不同版本使用驱动类可能不同):
<configuration> <!--数据库连接信息--> <property name="driverclass" value="com.mysql.cj.jdbc.driver"/><!-- <property name="driverclass" value="com.mysql.jdbc.driver"/>--> <property name="jdbcurl" value="jdbc:mysql://localhost:3306/mybatis?servertimezone=asia/shanghai"/> <property name="username" value="root"/> <property name="password" value="mimashi3124"/> <select id="selectone" parametertype="org.example.pojo.user" resulttype="org.example.pojo.user"> select * from user where id = #{id} and username =#{username} </select> <select id="selectlist" resulttype="org.example.pojo.user"> select * from user </select></configuration>
现在xml文件数据库信息也有了,sql语句定义也有了,还有什么问题呢?
我们实际中对sql的操作会涉及到不同的表,所以我们改进一下,把每个表的sql语句单独放在一个xml里,这样结构更清晰就容易维护。
优化以后的xml配置现在是这样了
sqlmapconfig.xml
<configuration> <!--数据库连接信息--> <property name="driverclass" value="com.mysql.cj.jdbc.driver"/> <!-- <property name="driverclass" value="com.mysql.jdbc.driver"/>--> <property name="jdbcurl" value="jdbc:mysql://localhost:3306/mybatis?servertimezone=asia/shanghai"/> <property name="username" value="root"/> <property name="password" value="mimashi3124"/> <!--引⼊sql配置信息--> <mapper resource="mapper.xml"></mapper></configuration>
mapper.xml
<mapper namespace="user"> <select id="selectone" parametertype="org.example.pojo.user" resulttype="org.example.pojo.user"> select * from user where id = #{id} and username =#{username} </select> <select id="selectlist" resulttype="org.example.pojo.user"> select * from user </select></mapper>
顺便定义一下业务实体user
public class user { private int id; private string username; 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; } @override public string tostring() { return "user{" + "id=" + id + ", username='" + username + '\'' + '}'; }}
2.读取配置文件读取完成以后以流的形式存在,不好操作,所以我们要做解析拿到信息,创建实体对象来存储。
configuration : 存放数据库基本信息、map<唯⼀标识,mapper> 唯⼀标识:namespace + "." + idmappedstatement:存放sql语句、输⼊参数类型、输出参数类型
xml解析我们使用dom4j
首先引入maven依赖
代码如下(mysql驱动版本根据实际使用mysql版本调整):
<properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <dependency> <groupid>mysql</groupid> <artifactid>mysql-connector-java</artifactid> <version>8.0.22</version> </dependency> <dependency> <groupid>c3p0</groupid> <artifactid>c3p0</artifactid> <version>0.9.1.2</version> </dependency> <dependency> <groupid>log4j</groupid> <artifactid>log4j</artifactid> <version>1.2.12</version> </dependency> <dependency> <groupid>junit</groupid> <artifactid>junit</artifactid> <version>4.10</version> </dependency> <dependency> <groupid>dom4j</groupid> <artifactid>dom4j</artifactid> <version>1.6.1</version> </dependency> <dependency> <groupid>jaxen</groupid> <artifactid>jaxen</artifactid> <version>1.1.6</version> </dependency> </dependencies>
数据库配置实体 configuration
public class configuration { //数据源 private datasource datasource; //map集合: key:statementid value:mappedstatement private map<string,mappedstatement> mappedstatementmap = new hashmap<>(); public datasource getdatasource() { return datasource; } public configuration setdatasource(datasource datasource) { this.datasource = datasource; return this; } public map<string, mappedstatement> getmappedstatementmap() { return mappedstatementmap; } public configuration setmappedstatementmap(map<string, mappedstatement> mappedstatementmap) { this.mappedstatementmap = mappedstatementmap; return this; }}
sql语句信息实体
public class mappedstatement { //id private string id; //sql语句 private string sql; //输⼊参数 private string parametertype; //输出参数 private string resulttype; public string getid() { return id; } public mappedstatement setid(string id) { this.id = id; return this; } public string getsql() { return sql; } public mappedstatement setsql(string sql) { this.sql = sql; return this; } public string getparametertype() { return parametertype; } public mappedstatement setparametertype(string parametertype) { this.parametertype = parametertype; return this; } public string getresulttype() { return resulttype; } public mappedstatement setresulttype(string resulttype) { this.resulttype = resulttype; return this; }}
顺便定义一个resources类来读取xml文件流
public class resources { public static inputstream getresourceassteam(string path) { return resources.class.getclassloader().getresourceasstream(path); }}
接下来就是实际的解析了,因为解析代码比较多,我们考虑封装类单独处理解析
定义xmlconfigbuilder类解析数据库配置信息
public class xmlconfigbuilder { private configuration configuration; public xmlconfigbuilder() { this.configuration = new configuration(); } public configuration parserconfiguration(inputstream inputstream) throws documentexception, propertyvetoexception, classnotfoundexception { document document = new saxreader().read(inputstream); element rootelement = document.getrootelement(); list<element> propertyelements = rootelement.selectnodes("//property"); properties properties = new properties(); for (element propertyelement : propertyelements) { string name = propertyelement.attributevalue("name"); string value = propertyelement.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); //将configuration传入xmlmapperbuilder中做sql语句解析。 xmlmapperbuilder xmlmapperbuilder = new xmlmapperbuilder(this.configuration); list<element> mapperelements = rootelement.selectnodes("//mapper"); for (element mapperelement : mapperelements) { string mapperpath = mapperelement.attributevalue("resource"); inputstream resourceasstream = this.getclass().getclassloader().getresourceasstream(mapperpath); xmlmapperbuilder.parse(resourceasstream); } return configuration; }}
定义xmlmapperbuilder类解析数据库配置信息
public class xmlmapperbuilder { private configuration configuration; public xmlmapperbuilder(configuration configuration) { this.configuration = configuration; } public void parse(inputstream inputstream) throws documentexception, classnotfoundexception { document document = new saxreader().read(inputstream); element rootelement = document.getrootelement(); string namespace = rootelement.attributevalue("namespace"); list<element> select = rootelement.selectnodes("select"); for (element element : select) { //id的值 string id = element.attributevalue("id"); string parametertype = element.attributevalue("parametertype"); //输⼊参数 string resulttype = element.attributevalue("resulttype"); //返回参数 //statementid,后续调用通过statementid,找到对应的sql执行 string key = namespace + "." + id; //sql语句 string texttrim = element.gettexttrim(); //封装 mappedstatement mappedstatement mappedstatement = new mappedstatement(); mappedstatement.setid(id); mappedstatement.setparametertype(parametertype); mappedstatement.setresulttype(resulttype); mappedstatement.setsql(texttrim); //填充 configuration configuration.getmappedstatementmap().put(key, mappedstatement); } }}
现在我们可以通过调用配置解析的方法拿到configuration对象了。但是我们实际使用,肯定是希望我给你配置信息、sql语句,再调用你的方法就返回结果了。
所以我们还需要定义一个数据库操作接口(类)
3.定义sql操作接口sqlsessionpublic interface sqlsession { //查询多个 public <e> list<e> selectlist(string statementid, object... param) throws exception; //查询一个 public <t> t selectone(string statementid,object... params) throws exception;}
对操作接口sqlsession做具体实现,这里主要是通过statementid找到对应的sql信息,进行执行
代码中simpleexcutor做真正的数据库语句执行、返回参数封装等操作
public class defaultsqlsession implements sqlsession { private configuration configuration; private executor simpleexcutor = new simpleexecutor(); public defaultsqlsession(configuration configuration) { this.configuration = configuration; } @override public <e> list<e> selectlist(string statementid, object... param) throws exception { mappedstatement mappedstatement = configuration.getmappedstatementmap().get(statementid); list<e> query = simpleexcutor.query(configuration, mappedstatement, param); return query; } @override public <t> t selectone(string statementid, object... params) throws exception { list<object> objects = selectlist(statementid, params); if (objects.size() == 1) { return (t) objects.get(0); } else { throw new runtimeexception("返回结果过多"); } }}
4.编写数据库执行逻辑数据库操作类defaultsqlsession中的selectlist方法调用到了simpleexcutor.query()方法
public class simpleexecutor implements executor { private connection connection = null; @override public <e> list<e> query(configuration configuration, mappedstatement mappedstatement, object[] params) throws exception { //获取连接 connection = configuration.getdatasource().getconnection(); // select * from user where id = #{id} and username = #{username} string sql = string sql = mappedstatement.getsql(); //对sql进⾏处理 boundsql boundsql = getboundsql(sql); // 3.获取预处理对象:preparedstatement preparedstatement preparedstatement = connection.preparestatement(boundsql.getsqltext()); // 4. 设置参数 //获取到了参数的全路径 string parametertype = mappedstatement.getparametertype(); class<?> parametertypeclass = getclasstype(parametertype); list<parametermapping> parametermappinglist = boundsql.getparametermappinglist(); for (int i = 0; i < parametermappinglist.size(); i++) { parametermapping parametermapping = parametermappinglist.get(i); string content = parametermapping.getcontent(); //反射 field declaredfield = parametertypeclass.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; } @override public void close() throws sqlexception { } private class<?> getclasstype(string parametertype) throws classnotfoundexception { if(parametertype!=null){ class<?> aclass = class.forname(parametertype); return aclass; } return null; } private boundsql getboundsql(string sql) { //标记处理类:主要是配合通⽤标记解析器generictokenparser类完成对配置⽂件等的解 析⼯作,其中 //tokenhandler主要完成处理 parametermappingtokenhandler parametermappingtokenhandler = new parametermappingtokenhandler(); //generictokenparser :通⽤的标记解析器,完成了代码⽚段中的占位符的解析,然后再根 据给定的 // 标记处理器(tokenhandler)来进⾏表达式的处理 //三个参数:分别为opentoken (开始标记)、closetoken (结束标记)、handler (标记处 理器) generictokenparser generictokenparser = new generictokenparser("#{", "}", parametermappingtokenhandler); string parse = generictokenparser.parse(sql); list<parametermapping> parametermappings = parametermappingtokenhandler.getparametermappings(); return new boundsql(parse, parametermappings); }}
上面的注释比较详细,流程为
根据对应的statementid获取到要执行的sql语句、调用参数、返回参数。
对sql的占位符进行解析、调用参数进行设置
根据解析到的入参字段,通过反射获取到对应的值,进行sql语句参数设定
执行sql语句,使用反射、内省,根据数据库表和实体的对应关系,完成对象属性的设置,最终返回结果。
通过以上步骤,我们获取到了数据库配置、sql语句信息。定义了数据库操作类sqlsession,但是我们并没有在什么地方调用解析配置文件。
我们还需要一个东西把两者给串起来,这里我们可以使用工厂模式来生成sqlsession
使用工厂模式创建sqlsession
public interface sqlsessionfactory { public sqlsession opensession();}
public class defaultsqlsessionfactory implements sqlsessionfactory{ private configuration configuration; public defaultsqlsessionfactory(configuration configuration) { this.configuration = configuration; } @override public sqlsession opensession() { return new defaultsqlsession(configuration); }}
同时为了屏蔽构建sqlsessionfactory工厂类时获取configuration的解析过程,我们可以使用构建者模式来获得一个sqlsessionfactory类。
public class sqlsessionfactorybuilder { public sqlsessionfactory build(inputstream inputstream) throws propertyvetoexception, documentexception, classnotfoundexception { xmlconfigbuilder xmlconfigerbuilder = new xmlconfigbuilder(); configuration configuration = xmlconfigerbuilder.parserconfiguration(inputstream); sqlsessionfactory sqlsessionfactory = new defaultsqlsessionfactory(configuration); return sqlsessionfactory; }}
5.调用测试终于好了,通过以上几个步骤我们现在可以具体调用执行代码了。
public static void main(string[] args) 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("盖伦"); user user2 = sqlsession.selectone("user.selectone", user); system.out.println(user2); list<user> users = sqlsession.selectlist("user.selectlist"); for (user user1 : users) { system.out.println(user1); } }
代码正确执行,输出
⾃定义框架优化上述⾃定义框架,解决了jdbc操作数据库带来的⼀些问题:例如频繁创建释放数据库连接,硬编
码,⼿动封装返回结果集等问题,现在我们继续来分析刚刚完成的⾃定义框架代码,有没有什么问题呢?
问题如下:
dao的实现类中存在重复的代码,整个操作的过程模板重复(创建sqlsession,调⽤sqlsession⽅ 法,关闭sqlsession)
dao的实现类中存在硬编码,调⽤sqlsession的⽅法时,参数statement的id硬编码
我们可以使用代理模式,生成代理对象,在调用之前获取到执行方法的方法名、具体类。这样我们就能获取到statementid。
为sqlsession类新增getmappper方法,获取代理对象
public interface sqlsession { public <e> list<e> selectlist(string statementid, object... param) throws exception; public <t> t selectone(string statementid,object... params) throws exception; //为dao接口生成代理实现类 public <t> t getmapper(class<?> mapperclass);}
public class defaultsqlsession implements sqlsession { private configuration configuration; private executor simpleexcutor = new simpleexecutor(); public defaultsqlsession(configuration configuration) { this.configuration = configuration; } @override public <e> list<e> selectlist(string statementid, object... param) throws exception { mappedstatement mappedstatement = configuration.getmappedstatementmap().get(statementid); list<e> query = simpleexcutor.query(configuration, mappedstatement, param); return query; } @override public <t> t selectone(string statementid, object... params) throws exception { list<object> objects = selectlist(statementid, params); if (objects.size() == 1) { return (t) objects.get(0); } else { throw new runtimeexception("返回结果过多"); } } @override public <t> t getmapper(class<?> mapperclass) { object proxyinstance = proxy.newproxyinstance(defaultsqlsession.class.getclassloader(), new class[]{mapperclass}, new invocationhandler() { @override public object invoke(object proxy, method method, object[] args) throws throwable { // selectone string methodname = method.getname(); // classname:namespace string classname = method.getdeclaringclass().getname(); //statementid string statementid = classname+'.'+methodname; type genericreturntype = method.getgenericreturntype(); //判断是否实现泛型类型参数化 if (genericreturntype instanceof parameterizedtype){ list<object> objects = selectlist(statementid,args); return objects; } return selectone(statementid,args); } }); return (t) proxyinstance; }}
定义业务数据dao接口
public interface iuserdao { //查询所有用户 public list<user> findall() throws exception; //根据条件进行用户查询 public user findbycondition(user user) throws exception;}
接下来我们只需获取到代理对象,调用方法即可。
public class main2 { public static void main(string[] args) throws exception { inputstream resourceassteam = resources.getresourceassteam("sqlmapconfig.xml"); sqlsessionfactory sqlsessionfactory = new sqlsessionfactorybuilder().build(resourceassteam); sqlsession sqlsession = sqlsessionfactory.opensession(); //获取到代理对象 iuserdao userdao = sqlsession.getmapper(iuserdao.class); list<user> all = userdao.findall(); for (user user1 : all) { system.out.println(user1); } }}
以上就是怎么用java手写持久层框架的详细内容。
该用户其它信息

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录 Product