一、手写mybatis框架 mybatis框架简介
bigegpt 2024-10-05 13:34 3 浏览
声明:内容来源于互联网,笔者主要进行了相关整理。
一、问题分析
1.jdbc数据库访问demo
public static void main(String[] args){
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try{
// 加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
// 通过驱动管理数据类获取数据库连接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis","root","111111");
// 定义sql语句 ?表示占位符
String sql = "select * from user where username = ?";
// 预编译sql
preparedStatement = connection.prepareStatement(sql);
// 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数的值
preparedStatement.setString(1,"zhangsan");
// 向数据库发送sql执行查询,查询出结果集
resultSet = preparedStatement.executeQuery();
// 遍历结果集
User user = new User();
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 {
// 释放连接
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
2.JDBC访问数据问题分析:
1.数据库配置信息存在硬编码--解决方案:配置文件
2.频繁创建释放数据库连接--解决方案:连接池
3.sql语句、设置参数、获取结果集参数均存在硬编码问题--解决方案:配置文件
4.手动封装返回结果集,较为繁琐。--解决方案:反射
二、自定义持久层框架设计思想:
1、使用端(项目):引入自定义持久层框架的jar包
提供两部分配置信息:数据库配置信息,sql配置信息:sql语句,参数类型,返回值类型
使用配置文件来提供这两部分信息
1)sqlMapConfig.xml:存储数据库配置信息,存放mapper.xml的全路径
2)mapping.xml:存放sql配置信息
2、自定义持久层框架(工具):本质就是对jdbc代码进行封装
1)加载配置文件:根据配置文件的路径,加载配置文件成字节输入流,存储在内存中
创建Resources类,方法InputStream getResourceAsStream(String path)
2)创建容器对象:两个JavaBean,存放的就是配置文件解析出来的容器对象
Configuration:核心配置类,存放sqlMapConfig.xml解析出来的内容
MappedStatement:映射配置类,存放mapping.xml解析出来的内容
3)解析配置配置文件:dom4j
创建类:SqlSessionFactoryBuilder 方法:build(InputStream in)
第一:使用dom4j解析配置文件,将解析出来的内容封装到容器对象中
第二:创建SqlSessionFactory对象:生产sqlSession会话对象(工厂模式)
4)创建SqlSessionFactory接口及实现类DefaultSqlSessionFactory
第一:openSession()生产sqlSession
5)创建SqlSession接口及实现类DefaultSession
定义数据库的curd操作:selectList(),selectOne(),update(),delete()
6)创建Executor接口及实现SimpleExecutor实现类
query(Configuration,MappedStatement,Object...params)执行的就是jdbc代码
三、实现过程
1.编写sqlMapConfig.xml
用于存放数据库连接配置,并关联mapper
<configuration>
<!--数据库配置信息-->
<dataSource>
<property name="driverClass" value="com.jdbc.Driver"></property>
<!-- ///表示本地的数据 -->
<property name="jdbcUrl" value="jdbc:mysql:///mybatis"></property>
<property name="username" value="root"></property>
<property name="password" value="111111"></property>
</dataSource>
<!--存放mapper.xml的全路径-->
<mapper resource="UserMapper.xml"></mapper>
</configuration>
2.编写sql配置信息
<mapper namespace="com.mybatis.dao.IUserDao">
<!--
select表示查询
id表示sql的标识
sql的唯一标识:namespace.id来组成,statementId
resultType返回值类型
-->
<select id="findAll" resultType="com.mybatis.pojo.User">
select * from user
</select>
<!--
参数传递:
User user = new User();
user.setId(1);
user.setUsername("tom")
使用#{xx}通过反射的方式,从paramType中获取xx属性的值作为sql的参数
-->
<select id="findByCondition" resultType="com.mybatis.pojo.User" paramType="com.mybatis.pojo.User">
select * from user where id = #{id} and username = #{username}
</select>
</mapper>
3.配置文件为输入流
public class Resources {
/**
* 根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中
* @param path
* @return
*/
public static InputStream getResourcesAsStream(String path){
InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
return resourceAsStream;
}
}
4.编写配置文件解析实体
1)mappedStatement解析mapper文件内容
public class MappedStatement {
/**
* id标识
*/
private String id;
/**
* 返回值类型
*/
private String resultType;
/**
* 参数类型
*/
private String paramType;
/**
* sql语句
*/
private String sql;
2)configuration对sqlMapConfig解析
public class Configuration {
/**
* 数据库配置信息
*/
private DataSource dateSource;
/**
* key:statementId就是sql的唯一标识,namespace+sql id
* value:一个封装好sql数据
*/
private Map<String,MappedStatement> mappedStatementMap = new HashMap();
5.获取SqlSessionFactory对象
1)解析sqlMapConfig.xml配置信息封装成Configuration
public class XMLConfigBuilder {
private Configuration configuration;
public XMLConfigBuilder(){
this.configuration = new Configuration();
}
/**
* 将配置文件解析成并封装成Configuration
* @param in
* @return
*/
public Configuration parseConfig(InputStream in) throws DocumentException, PropertyVetoException {
Document document = new SAXReader().read(in);
// 获取到跟<configuration>
Element configurationElt = document.getRootElement();
// 获取dataSource的配置,并使用连接池对象
List<Element> list = configurationElt.selectNodes("//property");
Properties properties = new Properties();
for(Element element:list){
String name = element.attributeValue("name");
String value = element.attributeValue("value");
properties.put(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.setDateSource(comboPooledDataSource);
// mapper.xml解析:拿到路径--字节输入流--dom4j解析
List<Element> mapperList = configurationElt.selectNodes("//mapper");
for(Element element:mapperList){
String mapperPath = element.attributeValue("resource");
InputStream mapperIn = Resources.getResourcesAsStream(mapperPath);
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
xmlMapperBuilder.parse(mapperIn);
}
return configuration;
}
}
2)mapper配置文件信息解析
public class XMLMapperBuilder {
private Configuration configuration;
public XMLMapperBuilder(Configuration configuration){
this.configuration = configuration;
}
/**
* 解析mapper.xml
* @param in
* @throws DocumentException
*/
public void parse(InputStream in) throws DocumentException {
Document document = new SAXReader().read(in);
Element rootElt = document.getRootElement();
String namespace = rootElt.attributeValue("namespace");
/**
* <select id="selectOne" resultType="com.persistence.pojo.User" paramType="com.persistence.pojo.User">
* select * from user where id = #{id} and username = #{username}
* </select>
*/
List<Element> elementList = rootElt.selectNodes("//select");
for(Element element:elementList){
String id = element.attributeValue("id");
String resultType = element.attributeValue("resultType");
String paramType = element.attributeValue("paramType");
String sql = element.getTextTrim();
MappedStatement mappedStatement = new MappedStatement();
mappedStatement.setId(id);
mappedStatement.setResultType(resultType);
mappedStatement.setParamType(paramType);
mappedStatement.setSql(sql);
String statementId = namespace+"."+id;
configuration.getMappedStatementMap().put(statementId,mappedStatement);
}
}
}
3)sqlSession工厂
//sqlSession接口
public interface SqlSessionFactory {
}
//sqlSession默认实现
public class DefaultSessionFactory implements SqlSessionFactory {
private Configuration configuration;
public DefaultSessionFactory(Configuration configuration){
this.configuration = configuration;
}
}
4)SqlSession工厂构造流程化
// 通过sqlSessionFactoryBuilder来串联整个步骤
public class SqlSessionFactoryBuilder {
/**
* 第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration中
* 第二:创建sqlSessionFactory对象
* @param in
* @return
*/
public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException {
//第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration中
XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
Configuration configuration = xmlConfigBuilder.parseConfig(in);
//第二:创建sqlSessionFactory对象,工厂类生产sqlSession会话对象
SqlSessionFactory sqlSessionFactory = new DefaultSessionFactory(configuration);
return sqlSessionFactory;
}
}
6.获取SqlSession,并执行sql
1)从sqlSession中获取sqlSession
// sqlSession工厂接口定义获取SqlSession的方法
public interface SqlSessionFactory {
SqlSession openSession();
}
//sqlSesssion工厂中实现获取sqlSession的方法
public class DefaultSessionFactory implements SqlSessionFactory {
private Configuration configuration;
public DefaultSessionFactory(Configuration configuration){
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return new DefaultSqlSession(configuration);
}
}
2)sqlSession中调用执行器,执行具体的方法
// sqlSession接口定义常用sql方法(curd)
public interface SqlSession {
/**
* 查询多个对象
* @param statementId
* @param param
* @param <E>
* @return
*/
<E> List<E> selectList(String statementId,Object...param);
/**
* 查询一个对象
* @param <E>
* @param statementId
* @param param
*/
<E> E selectOne(String statementId, Object...param);
}
//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... param) {
// 构建sql执行器
Executor executor = new SimpleExecutor();
// 获取mapper配置中所有sql配置信息
Map<String, MappedStatement> mappedStatementMap = configuration.getMappedStatementMap();
try {
// 执行当前sql
return executor.queryList(configuration,mappedStatementMap.get(statementId),param);
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IntrospectionException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
@Override
public <E> E selectOne(String statementId, Object... param) {
List<Object> objects = selectList(statementId,param);
if(objects.size()==1){
return (E)objects.get(0);
}else{
throw new RuntimeException("查询结果为空或者返回结果为多个");
}
}
3.Executor执行器
//exector执行器接口定义
public interface Executor {
<E> List<E> queryList(Configuration configuration, MappedStatement mappedStatement,Object...params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException, IntrospectionException, InvocationTargetException;
}
//执行器实现
public class SimpleExecutor implements Executor{
@Override
public <E> List<E> queryList(Configuration configuration, MappedStatement mappedStatement,Object...params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException, IntrospectionException, InvocationTargetException {
//1.注册驱动,活动连接
Connection connection = configuration.getDateSource().getConnection();
//2.获取sql select * from user where id = #{id} and username = #{username}
// 转换为jdbc的 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 paramType = mappedStatement.getParamType();
Class<?> paramTypeClass = getClassType(paramType);
List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
for(int i=0;i<parameterMappingList.size();i++){
ParameterMapping parameterMapping = parameterMappingList.get(i);
//获取sql参数中属性值(#{}中的值)
String content = parameterMapping.getContent();
//使用反射,根据返回获取content属性对应参数中的值
Field declaredField = paramTypeClass.getDeclaredField(content);
//暴力访问
declaredField.setAccessible(true);
Object o = declaredField.get(params[0]);
preparedStatement.setObject(i+1,o);
}
//5.执行sql
ResultSet resultSet = preparedStatement.executeQuery();
//6.封装返回结果集
String resultType = mappedStatement.getResultType();
Class<?> resultTypeClass = getClassType(resultType);
List<Object> objects = new ArrayList<Object>();
while(resultSet.next()){
Object o = resultTypeClass.newInstance();
ResultSetMetaData metaData = resultSet.getMetaData();
for(int i=0;i<metaData.getColumnCount();i++){
//字段名
String columnName = metaData.getColumnName(i+1);
//字段值
Object value = resultSet.getObject(columnName);
//使用反射,根据数据库表和实体的对应关系,完成封装
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName,resultTypeClass);
Method method = propertyDescriptor.getWriteMethod();
method.invoke(o,value);
}
objects.add(o);
}
return (List<E>)objects;
}
private Class<?> getClassType(String paramType) throws ClassNotFoundException {
if(paramType!=null){
Class<?> aClass = Class.forName(paramType);
return aClass;
}
return null;
}
/**
* 完成对#{}的解析工作,1.将#{}使用?进行代替,2.解析出#{}里面的值进行存储
* @param sql
* @return
*/
private BoundSql getBoundSql(String sql){
// 标记处理类:配置标记解析器完成对占位符的解析处理工作
ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
GenericTokenParser genericTokenParser = new GenericTokenParser("#{","}",parameterMappingTokenHandler);
//解析过后的sql
String parseSql = genericTokenParser.parse(sql);
//#{}解析出来的参数名称
List<ParameterMapping> parameterMappingList = parameterMappingTokenHandler.getParameterMappingList();
BoundSql boundSql = new BoundSql(parseSql,parameterMappingList);
return boundSql;
}
}
4)sql占位符分析
//参数中属性值#{}中的具体值
public class ParameterMapping {
private String content;
public ParameterMapping(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
//占位符解析器定义
public interface TokenHandler {
String handleToken(String content);
}
public class ParameterMappingTokenHandler implements TokenHandler{
private List<ParameterMapping> parameterMappingList = new ArrayList();
@Override
public String handleToken(String content) {
parameterMappingList.add(buildParameterMapping(content));
return "?";
}
private ParameterMapping buildParameterMapping(String content) {
ParameterMapping parameterMapping = new ParameterMapping(content);
return parameterMapping;
}
public List<ParameterMapping> getParameterMappingList() {
return parameterMappingList;
}
}
public class GenericTokenParser{
/**
* 开始标记
*/
private String openToken;
/**
* 结束标记
*/
private String closeToken;
/**
* 标记处理器
*/
private TokenHandler handler;
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
/**
* 解析#{}和${}
* @param text
* @return
* 该方法主要实现了配置文件、脚本等片段中占位符的解析、处理工作,并返回最终需要的数据。
* 其中,解析工作由该方法完成,处理工作是由处理器handler的handlerToken()方法完成
*/
public String parse(String text) {
// 验证参数问题,如果是null,就返回空字符串
if(text==null||text.isEmpty()){
return "";
}
// 下面继续验证是否包含开始标记和结束标记,默认不是占位符,直接原样返回即可,否则继续执行
int start = text.indexOf(openToken,0);
if(start==-1){
return text;
}
//把text转成字符串数组src,并且定义默认偏移量offset=0,存储最终需要返回的字符串的变量builder
//text变量中占位符对应的变量名expression。判断start是否大于-1(即text中是否存在openToken),如果存在则执行如下代码
char[] src = text.toCharArray();
int offset = 0;
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
while(start>-1){
//判断如果开始标记前如果有转义字符,就不作为openToken进行处理,否则继续处理
if(start>0&&src[start-1]=='\\'){
builder.append(src,offset,start-offset-1).append(openToken);
offset = start+openToken.length();
}else{
//重置expression变量,避免空指针或者老数据干扰
if(expression==null){
expression = new StringBuilder();
}else{
expression.setLength(0);
}
builder.append(src,offset,start-offset);
offset = start+openToken.length();
int end = text.indexOf(closeToken,offset);
while(end>-1){
//存在结束标记
if(end>offset&&src[end-1]=='\\'){
expression.append(src,offset,end-offset-1).append(closeToken);
}else{
expression.append(src,offset,end-offset);
offset=end+closeToken.length();
break;
}
}
if(end==-1){
builder.append(src,start,src.length-start);
offset=src.length;
}else{
builder.append(handler.handleToken(expression.toString()));
offset=end+closeToken.length();
}
}
start=text.indexOf(openToken,offset);
}
return builder.toString();
}
}
public class BoundSql {
/**
* 解析后的sql
*/
private String sqlText;
/**
* 参数
*/
private List<ParameterMapping> parameterMappingList;
public BoundSql(String sqlText, List<ParameterMapping> parameterMappingList) {
this.sqlText = sqlText;
this.parameterMappingList = parameterMappingList;
}
public String getSqlText() {
return sqlText;
}
public List<ParameterMapping> getParameterMappingList() {
return parameterMappingList;
}
}
5)测试流程
@Test
public void test() throws Exception {
InputStream in = Resources.getResourcesAsStream("sqlMapConfig.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(in);
SqlSession sqlSession = sessionFactory.openSession();
// 流程测试
User user = new User();
user.setId(1);
user.setUsername("zhangsan");
User user0 = sqlSession.selectOne("com.mybatis.dao.IUserDao.findByCondition",user);
System.out.println(user0);
}
6)编写dao和dao实现
public interface IUserDao {
List<User> findAll() throws Exception;
User findByCondition(User user) throws Exception;
}
public class UserDaoImpl implements IUserDao{
public List<User> findAll() throws Exception{
InputStream in = Resources.getResourcesAsStream("sqlMapConfig.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(in);
SqlSession sqlSession = sessionFactory.openSession();
// 调用
List<User> userList = sqlSession.selectList("com.mybatis.dao.IUserDao.findAll",null);
for(User user:userList){
System.out.println(user);
}
return userList;
}
public User findByCondition(User user) throws Exception{
InputStream in = Resources.getResourcesAsStream("sqlMapConfig.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(in);
SqlSession sqlSession = sessionFactory.openSession();
// 调用
User user2 = sqlSession.selectOne("com.mybatis.dao.IUserDao.findByCondition",user);
System.out.println(user2);
return user2;
}
}
测试流程
@Test
public void test() throws Exception {
InputStream in = Resources.getResourcesAsStream("sqlMapConfig.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(in);
SqlSession sqlSession = sessionFactory.openSession();
// 流程测试
User user = new User();
user.setId(1);
user.setUsername("zhangsan");
User user0 = sqlSession.selectOne("com.mybatis.dao.IUserDao.findByCondition",user);
System.out.println(user0);
// 传统方式调用(需要在dao每个方法中获取sqlSession,重复代码比较多)
IUserDao iUserDao = new UserDaoImpl();
User user1 = iUserDao.findByCondition(user);
System.out.println(user1);
}
此时发现,自定义持久层框架问题分析:
1.dao层使用自定义持久层框架,存在代码重复,整个操作的过程模板重复(加载配置文件,创建sqlSessionFactory,sqlSession).
2.statementId存在硬编码
解决方案:
使用代理模式生成dao层接口代理实现类
7)编写代理模式getMapper
public interface SqlSession {
/**
* 为dao接口生成代理实现类
* @param <T>
* @return
*/
<T> T getMappper(Class<?> mapperClass);
}
@Override
public <T> T getMappper(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 {
// proxy:当前代理对象的应用
// method:当前被调用方法的引用
// args:传递的参数
//底层还是去执行jdbc代码,根据不同情况,来执行selectOne或者selectList
//准备参数:
//参数1:statementId:sql语句的唯一表示,由mapper文件的namespace.id组成。
// 此时无法获取到,但是通常会按照一定规范来操作,即namespace值使用dao的全限定名,id使用方法名
String methodName = method.getName();
String className = method.getDeclaringClass().getName();
String statementId = className+"."+methodName;
//参数2:就是args
// 获取调用方法执行调用
Type genericReturnType = method.getGenericReturnType();
// 判断是否进行了泛型类型参数化
if(genericReturnType instanceof ParameterizedType){
List<Object> objects = selectList(statementId,args);
return objects;
}else{
return selectOne(statementId,args);
}
}
});
return (T)proxyInstance;
}
测试流程:
@Test
public void test() throws Exception {
InputStream in = Resources.getResourcesAsStream("sqlMapConfig.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(in);
SqlSession sqlSession = sessionFactory.openSession();
// 流程测试
User user = new User();
user.setId(1);
user.setUsername("zhangsan");
User user0 = sqlSession.selectOne("com.mybatis.dao.IUserDao.findByCondition",user);
System.out.println(user0);
// 传统方式调用(需要在dao每个方法中获取sqlSession,重复代码比较多)
IUserDao iUserDao = new UserDaoImpl();
User user1 = iUserDao.findByCondition(user);
System.out.println(user1);
// 使用代理对象来调用方法(统一在外层处理一次)
IUserDao userDao = sqlSession.getMappper(IUserDao.class);
List<User> userList = userDao.findAll();
for (User user2:userList){
System.out.println(user2);
}
}
相关推荐
- C#.NET Autofac 详解(c# autoit)
-
简介Autofac是一个成熟的、功能丰富的.NET依赖注入(DI)容器。相比于内置容器,它额外提供:模块化注册、装饰器(Decorator)、拦截器(Interceptor)、强o的属性/方法注...
- webapi 全流程(webapi怎么部署)
-
C#中的WebAPIMinimalApi没有控制器,普通api有控制器,MinimalApi是直达型,精简了很多中间代码,广泛适用于微服务架构MinimalApi一切都在组控制台应用程序类【Progr...
- .NET外挂系列:3. 了解 harmony 中灵活的纯手工注入方式
-
一:背景1.讲故事上一篇我们讲到了注解特性,harmony在内部提供了20个HarmonyPatch重载方法尽可能的让大家满足业务开发,那时候我也说了,特性虽然简单粗暴,但只能解决95%...
- C# 使用SemanticKernel调用本地大模型deepseek
-
一、先使用ollama部署好deepseek大模型。具体部署请看前面的头条使用ollama进行本地化部署deepseek大模型二、创建一个空的控制台dotnetnewconsole//添加依赖...
- C#.NET 中间件详解(.net core中间件use和run)
-
简介中间件(Middleware)是ASP.NETCore的核心组件,用于处理HTTP请求和响应的管道机制。它是基于管道模型的轻量级、模块化设计,允许开发者在请求处理过程中插入自定义逻辑。...
- IoC 自动注入:让依赖注册不再重复劳动
-
在ASP.NETCore中,IoC(控制反转)功能通过依赖注入(DI)实现。ASP.NETCore有一个内置的依赖注入容器,可以自动完成依赖注入。我们可以结合反射、特性或程序集扫描来实现自动...
- C#.NET 依赖注入详解(c#依赖注入的三种方式)
-
简介在C#.NET中,依赖注入(DependencyInjection,简称DI)是一种设计模式,用于实现控制反转(InversionofControl,IoC),以降低代码耦合、提高可...
- C#从零开始实现一个特性的自动注入功能
-
在现代软件开发中,依赖注入(DependencyInjection,DI)是实现松耦合、模块化和可测试代码的一个重要实践。C#提供了优秀的DI容器,如ASP.NETCore中自带的Micr...
- C#.NET 仓储模式详解(c#仓库货物管理系统)
-
简介仓储模式(RepositoryPattern)是一种数据访问抽象模式,它在领域模型和数据访问层之间创建了一个隔离层,使得领域模型无需直接与数据访问逻辑交互。仓储模式的核心思想是将数据访问逻辑封装...
- C#.NET 泛型详解(c# 泛型 滥用)
-
简介泛型(Generics)是指在类型或方法定义时使用类型参数,以实现类型安全、可重用和高性能的数据结构与算法为什么需要泛型类型安全防止“装箱/拆箱”带来的性能损耗,并在编译时检测类型错误。可重用同一...
- 数据分析-相关性分析(相关性 分析)
-
相关性分析是一种统计方法,用于衡量两个或多个变量之间的关系强度和方向。它通过计算相关系数来量化变量间的线性关系,从而帮助理解变量之间的相互影响。相关性分析常用于数据探索和假设检验,是数据分析和统计建模...
- geom_smooth()函数-R语言ggplot2快速入门18
-
在每节,先运行以下这几行程序。library(ggplot2)library(ggpubr)library(ggtext)#用于个性化图表library(dplyr)#用于数据处理p...
- 规范申报易错要素解析(规范申报易错要素解析)
-
为什么要规范申报?规范申报是以满足海关监管、征税、统计等工作为目的,纳税义务人及其代理人依法向海关如实申报的行为,也是海关审接单环节依法监管的重要工作。企业申报的内容须符合《中华人民共和国海关进出口货...
- 「Eurora」海关编码归类 全球海关编码查询 关务服务
-
海关编码是什么? 海关编码即HS编码,为编码协调制度的简称。 其全称为《商品名称及编码协调制度的国际公约》(InternationalConventionforHarmonizedCo...
- 9月1日起,河南省税务部门对豆制品加工业试行新政7类豆制品均适用投入产出法
-
全媒体记者杨晓川报道9月2日,记者从税务部门获悉,为减轻纳税人税收负担,完善农产品增值税进项税额抵扣机制,根据相关规定,结合我省实际情况,经广泛调查研究和征求意见,从9月1日起,我省税务部门对豆制品...
- 一周热门
- 最近发表
- 标签列表
-
- mybatiscollection (79)
- mqtt服务器 (88)
- keyerror (78)
- c#map (65)
- xftp6 (83)
- bt搜索 (75)
- c#var (76)
- xcode-select (66)
- mysql授权 (74)
- 下载测试 (70)
- linuxlink (65)
- pythonwget (67)
- androidinclude (65)
- libcrypto.so (74)
- linux安装minio (74)
- ubuntuunzip (67)
- vscode使用技巧 (83)
- secure-file-priv (67)
- vue阻止冒泡 (67)
- jquery跨域 (68)
- php写入文件 (73)
- kafkatools (66)
- mysql导出数据库 (66)
- jquery鼠标移入移出 (71)
- 取小数点后两位的函数 (73)