自己动手写一个持久层框架(什么是持久层框架)
ccwgpt 2024-10-09 08:47 22 浏览 0 评论
1. JDBC问题分析
我们来看一段JDBC的代码:
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//1. 加载数据库驱动
Class.forName("com.mysql.jdbc.Drive");
//2. 通过驱动管理类获取数据库链接
connection = DriverManager.getConnection("jdbc:mysql://hocalhost:3306/mybatis?characterEncoding=utf-8",
"root","root");
//3. 定义SQL语句 ?表示占位符
String sql = "SELECT * FROM user WHERE username = ?";
//4. 获取预处理对象Statement
preparedStatement = connection.prepareStatement(sql);
//5. 设置参数,第一个参数为SQL语句中参数的序号(从1开始),第二个参数为设置的参数值
preparedStatement.setString(1,"tom");
//6. 向数据库发出SQL执行查询,查询出结果集
resultSet = preparedStatement.executeQuery();
//7. 遍历查询结果集
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 (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
可以看到,直接使用JDBC开发是存在一些问题的,我们来分析下:
问题分析:
- 数据库配置信息存在硬编码问题
- 频繁创建、释放数据库链接
//1. 加载数据库驱动
Class.forName("com.mysql.jdbc.Drive");
//2. 通过驱动管理类获取数据库链接
connection = DriverManager.getConnection("jdbc:mysql://hocalhost:3306/mybatis?characterEncoding=utf-8","root","root");
- sql语句、设置参数、获取结果集均存在硬编码问题
//3. 定义SQL语句 ?表示占位符
String sql = "SELECT * FROM user WHERE username = ?";
//4. 获取预处理对象Statement
preparedStatement = connection.prepareStatement(sql);
//5. 设置参数,第一个参数为SQL语句中参数的序号(从1开始),第二个参数为设置的参数值
preparedStatement.setString(1,"tom");
//6. 向数据库发出SQL执行查询,查询出结果集
resultSet = preparedStatement.executeQuery();
int id = resultSet.getInt("id");
String userName = resultSet.getString("username");
- 手动封装返回结果集 较为繁琐
//7. 遍历查询结果集
while (resultSet.next()){
int id = resultSet.getInt("id");
String userName = resultSet.getString("username");
//封装User
user.setId(id);
user.setUserName(userName);
}
System.out.println(user);
解决思路:
- 写在配置文件中
- 连接池(c3p0、dbcp、德鲁伊...)
- 配置文件 (和1放一起吗? No,经常变动和不经常变动的不要放在一起)
- 反射、内省
下面根据这个解决思路,自己动手写一个持久层框架,写框架之前分析这个框架需要做什么
2. 自定义框架思路分析
使用端(项目):
- 引入自定义持久层框架的jar包
- 提供两部分配置信息:
- 数据库配置信息
- SQL配置信息(SQL语句)
- 使用配置文件来提供这些信息: sqlMapConfig.xml :存放数据库的配置信息mapper.xml :存放SQL配置信息
自定义持久层框架(工程):
持久层框架的本质就是对JDBC代码进行了封装
- 加载配置文件:根据配置文件的路径加载配置文件成字节输入流,存储内存中 创建Resources类 方法:getResourceAsStream(String path) “ Q: getResourceAsStearm方法需要执行两次分别加载sqlMapConfig额和mapper吗? A:可以但没必要,我们可以在sqlMapConfig.xml中写入mapper.xml的全路径即可 ”
- 创建两个javaBean:(容器对象):存放的就是配置文件解析出来的内容 Configuration:核心配置类:存放sqlMapConfig.xml解析出来的内容MappedStatement:映射配置类:存放mapper.xml解析出来的内容
- 解析配置文件:使用dom4j 创建类:SqlSessionFactoryBuilder 方法:build(InputStream in) 这个流就是刚才存在内存中的使用dom4j解析配置文件,将解析出来的内容封装到容器对象中创建SqlSessionFactory对象;生产sqlSession:会话对象(工厂模式 降低耦合,根据不同需求生产不同状态的对象)
- 创建sqlSessionFactory接口及实现类DefaultSqlSessionFactory openSession(); 生产sqlSession
- 创建SqlSession接口及实现类DefaultSession 定义对数据库的CRUD操作,例如: selectList()selectOne()update()delete()...
- 创建Executor接口及实现类SimpleExecutor实现类 query(Configuration con,MappedStatement ms,Object ...param);执行JDBC代码,Object ...param具体的参数值,可变参;
3. 创建表并编写测试类
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'lucy');
INSERT INTO `user` VALUES (2, 'tom');
INSERT INTO `user` VALUES (3, 'jack');
SET FOREIGN_KEY_CHECKS = 1;
1. 创建一个Maven项目—— Ipersistence_test
2. 在resource中创建sqlMapConfig.xml 和 UserMapper.xml
UserMapper.xml
<mapper namespace="user">
<!--sql的唯一标识:namespace.id来组成 :statementId-->
<select id="selectList" resultType="com.dxh.pojo.User">
select * from user
</select>
<select id="selectOne" resultType="com.dxh.pojo.User" paramterType="com.dxh.pojo.User">
select * from user where id = #{id} and username = #{userName}
</select>
</mapper>
“
Q: 为什么要有namespace和id ? A: 当一个*Mapper.xml中有多条sql时,无法区分具体是哪一条所以增加 id 如果有UserMapper.xml和ProductMapper.xml,假设他们的查询的id都为”selectList“,那么将无法区分具体是查询user还是查询product的。 所以增加 namespace namespace.id 组成sql的唯一标识,也称为statementId
”
sqlMapConfig.xml
<configuration>
<!--数据库配置信息 -->
<dataSource>
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///zdy_mybatis"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</dataSource>
<!--存放mapper.xml全路径-->
<mapper resource="UserMapper.xml"></mapper>
</configuration>
4. 开始编写持久层框架
自定义持久层框架(工程):
“
本质就是对JDBC代码进行了封装
加载配置文件:根据配置文件的路径加载配置文件成字节输入流,存储内存中 创建Resources类 方法:getResourceAsStream(String path)
创建两个javaBean:(容器对象):存放的就是配置文件解析出来的内容 Configuration:核心配置类:存放sqlMapConfig.xml解析出来的内容MappedStatement:映射配置类:存放mapper.xml解析出来的内容
解析配置文件:使用dom4j 创建类:SqlSessionFactoryBuilder 方法:build(InputStream in) 这个流就是刚才存在内存中的使用dom4j解析配置文件,将解析出来的内容封装到容器对象中创建SqlSessionFactory对象;生产sqlSession:会话对象(工厂模式 降低耦合,根据不同需求生产不同状态的对象)
创建sqlSessionFactory接口及实现类DefaultSqlSessionFactory openSession(); 生产sqlSession
创建SqlSession接口及实现类DefaultSession 定义对数据库的CRUD操作
创建Executor接口及实现类SimpleExecutor实现类 query(Configuration con,MappedStatement ms,Object ...param);执行JDBC代码,Object ...param具体的参数值,可变参;
”
我们之前已经对持久层框架进行了分析,需要做6部分组成,如下:
1. 加载配置文件
我们要把用户端的配置文件成字节输入流并存到内存中:
新建Resource类,提供一个static InputStream getResourceAsStream(String path)方法,并返回inputstream
package com.dxh.io;
import java.io.InputStream;
public class Resource {
//根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中
public static InputStream getResourceAsStream(String path){
InputStream resourceAsStream = Resource.class.getClassLoader().getResourceAsStream(path);
return resourceAsStream;
}
}
2. 创建JavaBean(容器对象)
之前我们说到,要把解析出来的配置文件封装成对象。
- MappedStatement (存放SQL信息)
- Configuration (存放数据库配置信息)
// MappedStatement,我们存放SQL的信息
package com.dxh.pojo;
public class MappedStatement {
// id标识
private String id;
//返回值类型
private String resultType;
//参数值类型
private String paramterType;
//sql语句
private String sql;
getset省略...
}
这里我们把封装好的MappedStatement对象也放在Configuration中,同时我们不存放数据库的url、username...了,直接存放DataSource
package com.dxh.pojo;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
public class Configuration {
private DataSource dataSource;
/**
* key statementId (就是namespace.id)
* value:封装好的MappedStatement对象
*/
Map<String,MappedStatement> mappedStatementMap = new HashMap<>();
getset省略...
}
3.解析xml文件
这一步我们解析两个xml文件sqlMapConfig.xml、mapper.xml
我们首先把解析的过程封装起来:新建XMLConfigBuild.java
package com.dxh.config;
import com.dxh.io.Resource;
import com.dxh.pojo.Configuration;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.beans.PropertyVetoException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;
public class XMLConfigBuild {
private Configuration configuration;
public XMLConfigBuild() {
this.configuration = new Configuration();
}
/**
* 该方法就是将配置文件进行解析(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);
}
//C3P0连接池
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 resourceAsStream = Resource.getResourceAsStream(mapperPath);
//dom4j解析
// 因为解析完成后的MappedStatement要放在Configuration里,所以传入一个configuration进去
XMLMapperBuild xmlMapperBuild = new XMLMapperBuild(configuration);
xmlMapperBuild.parse(resourceAsStream);
}
return configuration;
}
}
3.1 解析Mapper.xml文件:
package com.dxh.config;
import com.dxh.pojo.Configuration;
import com.dxh.pojo.MappedStatement;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.List;
public class XMLMapperBuild {
private Configuration configuration;
public XMLMapperBuild(Configuration configuration) {
this.configuration = configuration;
}
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.setParamterType(paramterType);
mappedStatement.setResultType(resultType);
mappedStatement.setSql(sqlText);
String key = namespace+"."+id;
configuration.getMappedStatementMap().put(key,mappedStatement);
}
}
}
很容易理解,因为我们解析后要返回Configuration对象,所以我们需要声明一个Configuration 并初始化。
我们把加载文件后的流传入,通过dom4j解析,并通过ComboPooledDataSource(C3P0连接池)生成我们需要的DataSource,并存入Configuration对象中。
Mapper.xml解析方式同理。
3.2 创建SqlSessionFactoryBuilder类: 有了上述两个解析方法后,我们创建一个类,用来调用这个方法,同时这个类返回SqlSessionFacetory
SqlSessionFacetory:用来生产sqlSession:sqlSession就是会话对象(工厂模式 降低耦合,根据不同需求生产不同状态的对象)
package com.dxh.sqlSession;
import com.dxh.config.XMLConfigBuild;
import com.dxh.pojo.Configuration;
import org.dom4j.DocumentException;
import java.beans.PropertyVetoException;
import java.io.InputStream;
public class SqlSessionFacetoryBuild {
public SqlSessionFacetory build(InputStream in) throws DocumentException, PropertyVetoException {
//1. 使用dom4j解析配置文件,将解析出来的内容封装到configuration中
XMLConfigBuild xmlConfigBuild = new XMLConfigBuild();
Configuration configuration = xmlConfigBuild.parseConfig(in);
//2. 创建sqlSessionFactory对象 工厂类:生产sqlSession:会话对象,与数据库交互的增删改查都封装在sqlSession中
DefaultSqlSessionFactory sqlSessionFacetory = new DefaultSqlSessionFactory(configuration);
return sqlSessionFacetory;
}
}
4. 创建SqlSessionFacetory接口和实现类
基于开闭原则我们创建SqlSessionFacetory接口和实现类DefaultSqlSessionFactory
接口中我们定义openSession()方法,用于生产SqlSession
package com.dxh.sqlSession;
public interface SqlSessionFacetory {
public SqlSession openSession();
}
package com.dxh.sqlSession;
import com.dxh.pojo.Configuration;
public class DefaultSqlSessionFactory implements SqlSessionFacetory{
private Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return new DefaultSqlSession(configuration);
}
}
同样我们在DefaultSqlSessionFactory中传入Configuration,Configuration需要我们一直往下传递
5.创建SqlSession接口以及它的实现类
在接口中,我定义两个方法:
因为参数类型和个数我们都不知道,所以我们使用泛型,同时,传入statementId(namespace、. 、id 组成)
这里selectOne方法和selectList方法的参数结构都是一样的,所以我们可以通过selectList.get(0)的方式得到一个返回结果。而selectList中则是重点,我们需要创建一个对象SimpleExecutor并在其中执行SQL
6.创建Executor接口及实现类SimpleExecutor实现类
这里的实现大致可分为6部分:
- 注册驱动,获取链接:通过传入的configuration得到datasource,然后调用getConnection()得到链接
- 获取SQL语句 我们mapper.xml的SQL语句是这样的select * from user where id = #{id} and username = #{username},需要转换为select * from user where id = ? and username =? 这样JDBC才能认。同时我们需要把#{}中的参数赋值到?这个占位符处。 这里我们定义了一个getBoundSql方法,通过标记处理类(配置标记解析器来完成对占位符的处理工作)解析成带有?的sql,同时把#{}里面的内容传入ParameterMapping中。
- 通过connection.prepareStatement(boundSql.getSqlText())得到预处理对象
- 设置参数,我们在mapper.xml文件中已经写了paramterType,有了入参类型的全路径我们可以通过反射获取其对象。 根据ParameterMapping中存入的的#{}中的内容,通过反射获取其值,然后与下标绑定。
- 执行SQL
- 封装返回结果集 这里使用内省
- 返回(List<E>) objects
7.结束
此时我们框架中的代码已经写完了。
8.测试类
package com.dxh.test;
import com.dxh.io.Resource;
import com.dxh.pojo.User;
import com.dxh.sqlSession.SqlSession;
import com.dxh.sqlSession.SqlSessionFacetory;
import com.dxh.sqlSession.SqlSessionFacetoryBuild;
import org.dom4j.DocumentException;
import org.junit.Test;
import java.beans.PropertyVetoException;
import java.io.InputStream;
import java.util.List;
public class IPersistenceTest {
@Test
public void test() throws Exception {
InputStream resourceAsStream = Resource.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFacetory sqlSessionFacetory = new SqlSessionFacetoryBuild().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFacetory.openSession();
User user = new User();
user.setId(1);
user.setUsername("lucy");
User user2 = sqlSession.selectOne("user.selectOne",user);
System.out.println(user2.toString());
// List<User> userList = sqlSession.selectList("user.selectList");
// for (User user1 : userList) {
// System.out.println(user1);
// }
}
}
执行结果:
User{id=1, username='lucy'}
最终的目录结构:
image-20201108015103475
5. 自定义持久层框架的优化
我们的自定义持久层框架已经完成了,下面我们分析下这个框架,看看还有没有明显的弊端。
首先,我们先模仿正常的项目,创建一个Dao层
package com.dxh.dao;
import com.dxh.pojo.User;
import java.util.List;
public interface IUserDao {
//查询所有用户
public List<User> findAll() throws Exception;
//根据条件进行查询
public User findByCondition(User user) throws Exception;
}
package com.dxh.dao;
import com.dxh.io.Resource;
import com.dxh.pojo.User;
import com.dxh.sqlSession.SqlSession;
import com.dxh.sqlSession.SqlSessionFacetory;
import com.dxh.sqlSession.SqlSessionFacetoryBuild;
import java.io.InputStream;
import java.util.List;
public class IUserDaoImpl implements IUserDao {
@Override
public List<User> findAll() throws Exception {
InputStream resourceAsStream = Resource.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFacetory sqlSessionFacetory = new SqlSessionFacetoryBuild().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFacetory.openSession();
List<User> userList = sqlSession.selectList("user.selectList");
return userList;
}
@Override
public User findByCondition(User user) throws Exception {
InputStream resourceAsStream = Resource.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFacetory sqlSessionFacetory = new SqlSessionFacetoryBuild().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFacetory.openSession();
User user2 = sqlSession.selectOne("user.selectOne",user);
return user2;
}
}
问题分析:
- 很明显存在代码重复的问题,他们的前三句话都一样(加载配置文件、创建SqlSessionFacetory、生产SqlSeesion) InputStream resourceAsStream = Resource.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFacetory sqlSessionFacetory = new SqlSessionFacetoryBuild().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFacetory.openSession(); - statementId存在硬编码问题 List<User> userList = sqlSession.selectList("user.selectList");
User user2 = sqlSession.selectOne("user.selectOne",user);
解决思路:
使用代理模式生成Dao层代理实现类。
在SqlSession接口中增加一个方法并实现:
我们使用Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法来生产代理对象。一会我们再来实现invoke方法。
那么此时我们如果再想执行方法应该这样做:
IUserDao iUserDao = sqlSession.getMapper(IUserDao.class);
List<User> all = iUserDao.findAll();
lll
- 通过sqlSession.getMapper()方法获得代理对象
- 通过代理对象调用findAll()方法
- 执行invoke方法
我们来看看invoke方法:
- Object proxy :当前代理对象的引用
- Method method :当前被调用方法的引用 比如我们当前的代理对象iUserDao调用的是findAll()方法,而method就是findAll方法的引用
- Object[] args : 传递的参数,比如我们想要根据条件查询
编写invoke()方法:
我们要首先明确一点,不论如何封装,底层都还是执行JDBC代码,那么我们就要根据不同情况 调用selectList或者selectOne。
此时就有一个疑问了:selectList和selectOne都需要一个参数——statementId,而此时我们是拿不到statementId的。
但是我们可以根据method对象得到方法名,和方法所在类的全类名。
因此我们需要规范下statementId的组成:
“
statementId = namespace.id = 方法所在类的全类名.方法名
”
修改UserMapper.xml
测试:
package com.dxh.test;
import com.dxh.dao.IUserDao;
import com.dxh.io.Resource;
import com.dxh.pojo.User;
import com.dxh.sqlSession.SqlSession;
import com.dxh.sqlSession.SqlSessionFacetory;
import com.dxh.sqlSession.SqlSessionFacetoryBuild;
import org.dom4j.DocumentException;
import org.junit.Test;
import java.beans.PropertyVetoException;
import java.io.InputStream;
import java.util.List;
public class IPersistenceTest {
@Test
public void test() throws Exception {
InputStream resourceAsStream = Resource.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFacetory sqlSessionFacetory = new SqlSessionFacetoryBuild().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFacetory.openSession();
IUserDao iUserDao = sqlSession.getMapper(IUserDao.class);
List<User> all = iUserDao.findAll();
System.out.println(all);
//打印结果:[User{id=1, username='lucy'}, User{id=2, username='李四'}, User{id=3, username='null'}]
User user1 = iUserDao.findByCondition(user);
System.out.println(user1);
//User{id=1, username='lucy'}
}
}
来源:https://www.cnblogs.com/isdxh/p/13953368.html
作者:DXH's Blog
相关推荐
- 滨州维修服务部“一区一策”强服务
-
今年以来,胜利油田地面工程维修中心滨州维修服务部探索实施“一区一策”服务模式,持续拓展新技术应用场景,以优质的服务、先进的技术,助力解决管理区各类维修难题。服务部坚持问题导向,常态化对服务范围内的13...
- 谷歌A2A协议和MCP协议有什么区别?A2A和MCP的差异是什么?
-
在人工智能的快速发展中,如何实现AI模型与外部系统的高效协作成为关键问题。谷歌主导的A2A协议(Agent-to-AgentProtocol)和Anthropic公司提出的MCP协议(ModelC...
- 谷歌大脑用架构搜索发现更好的特征金字塔结构,超越Mask-RCNN等
-
【新智元导读】谷歌大脑的研究人员发表最新成果,他们采用神经结构搜索发现了一种新的特征金字塔结构NAS-FPN,可实现比MaskR-CNN、FPN、SSD更快更好的目标检测。目前用于目标检测的最先...
- 一文彻底搞懂谷歌的Agent2Agent(A2A)协议
-
前段时间,相信大家都被谷歌发布的Agent2Agent开源协议刷屏了,简称A2A。谷歌官方也表示,A2A是在MCP之后的补充,也就是MCP可以强化大模型/Agent的能力,但每个大模型/Agent互为...
- 谷歌提出创新神经记忆架构,突破Transformer长上下文限制
-
让AI模型拥有人类的记忆能力一直是学界关注的重要课题。传统的深度学习模型虽然在许多任务上取得了显著成效,但在处理需要长期记忆的任务时往往力不从心。就像人类可以轻松记住数天前看过的文章重点,但目前的...
- 不懂设计?AI助力,人人都能成为UI设计师!
-
最近公司UI资源十分紧张,急需要通过AI来解决UI人员不足问题,我在网上发现了几款AI应用非常适合用来进行UI设计。以下是一些目前非常流行且功能强大的工具,它们能够提高UI设计效率,并帮助设计师创造出...
- 速来!手把手教你用AI完成UI界面设计
-
晨星技术说晨星技术小课堂第二季谭同学-联想晨星用户体验设计师-【晨星小课堂】讲师通过简单、清晰的语言描述就能够用几十秒自动生成一组可编辑的UI界面,AIGC对于UI设计师而言已经逐步发展成了帮助我们...
- 「分享」一端录制,多端使用的便捷 UI 自动化测试工具,开源
-
一、项目介绍Recorder是一款UI录制和回归测试工具,用于录制浏览器页面UI的操作。通过UIRecorder的录制功能,可以在自测的同时,完成测试过程的录制,生成JavaScr...
- APP自动化测试系列之Appium介绍及运行原理
-
在面试APP自动化时,有的面试官可能会问Appium的运行原理,以下介绍Appium运行原理。Appium介绍Appium概念Appium是一个开源测试自动化框架,可用于原生,混合和移动Web应用程序...
- 【推荐】一个基于 SpringBoot 框架开发的 OA 办公自动化系统
-
如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!项目介绍oasys是一个基于springboot框架开发的OA办公自动化系统,旨在提高组织的日常运作和管理...
- 自动化实践之:从UI到接口,Playwright给你全包了!
-
作者:京东保险宋阳1背景在车险系统中,对接保司的数量众多。每当系统有新功能迭代后,基本上各个保司的报价流程都需要进行回归测试。由于保司数量多,回归测试的场景也会变得重复而繁琐,给测试团队带来了巨大的...
- 销帮帮CRM移动端UI自动化测试实践:Playwright的落地与应用
-
实施背景销帮帮自2015年成立以来,移动端UI自动化测试的落地举步维艰,移动端的UI自动化测试一直以来都未取得良好的落地。然而移动互联网时代,怎样落地移动端的UI自动化测试以快速稳定进行移动端的端到端...
- 编写自动化框架不知道该如何记录日志吗?3个方法打包呈现给你。
-
目录结构1.loguru介绍1.1什么是日志?程序运行过程中,难免会遇到各种报错。如果这种报错是在本地发现的,你还可以进行debug。但是如果程序已经上线了,你就不能使用debug方式了...
- 聊聊Python自动化脚本部署服务器全流程(详细)
-
来源:AirPython作者:星安果1.前言大家好,我是安果!日常编写的Python自动化程序,如果在本地运行稳定后,就可以考虑将它部署到服务器,结合定时任务完全解放双手但是,由于自动化程序与平...
- 「干货分享」推荐5个可以让你事半功倍的Python自动化脚本
-
作者:俊欣来源:关于数据分析与可视化相信大家都听说自动化流水线、自动化办公等专业术语,在尽量少的人工干预的情况下,机器就可以根据固定的程序指令来完成任务,大大提高了工作效率。今天小编来为大家介绍几个P...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- MVC框架 (46)
- spring框架 (46)
- 框架图 (58)
- flask框架 (53)
- quartz框架 (51)
- abp框架 (47)
- jpa框架 (47)
- springmvc框架 (49)
- 分布式事务框架 (65)
- scrapy框架 (56)
- shiro框架 (61)
- 定时任务框架 (56)
- java日志框架 (61)
- JAVA集合框架 (47)
- mfc框架 (52)
- abb框架断路器 (48)
- ui自动化框架 (47)
- grpc框架 (55)
- ppt框架 (48)
- 内联框架 (52)
- cad怎么画框架 (58)
- ps怎么画框架 (47)
- ssm框架实现登录注册 (49)
- oracle字符串长度 (48)
- oracle提交事务 (47)