MyBatis是一款流行的ORM框架,其源码实现是非常值得学习的。以下是一些可以借鉴的思维方式:

  1. 设计模式的应用:MyBatis的源码中应用了许多常见的设计模式,例如工厂模式、代理模式、模板方法模式等。这些设计模式的应用使得代码更加灵活、可扩展、易于维护。
  2. 面向对象的思维方式:MyBatis的源码中充分体现了面向对象的思维方式,例如封装、继承、多态等。通过合理的抽象和封装,使得代码的可读性和可维护性得到了极大的提高。
  3. 性能优化的思维方式:MyBatis的源码中考虑了大量的性能优化策略,例如缓存、批量操作、预编译等。这些优化策略可以在其他Java项目中借鉴和应用,提高系统的性能和效率。
  4. 代码组织和架构设计的思维方式:MyBatis的源码非常规范和清晰,代码组织和架构设计也非常合理。通过合理的模块划分和代码组织,使得代码的可读性、可维护性和可扩展性得到了很好的保证。
  5. 单元测试和持续集成的思维方式:MyBatis的源码中有大量的单元测试和持续集成的相关实践,这些实践可以帮助Java工程师更好地保证代码的质量和稳定性。

以下是一些MyBatis源码中的代码示例,以供Java工程师参考:

  1. 工厂模式的应用

MyBatis中使用了SqlSessionFactoryBuilder类来创建SqlSessionFactory对象,它是一个工厂模式的应用。它的代码实现如下所示:

public class SqlSessionFactoryBuilder {
    
    public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
            Configuration config = parser.parse();
            return new DefaultSqlSessionFactory(config);
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        }
    }
}
  1. 代理模式的应用

MyBatis中使用了MapperProxy类来实现Mapper接口的代理,它是一个代理模式的应用。它的代码实现如下所示:

public class MapperProxy<T> implements InvocationHandler {
    
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    
    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
    }
    
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }
            final MapperMethod mapperMethod = cachedMapperMethod(method);
            return mapperMethod.execute(sqlSession, args);
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
    }
    
    private MapperMethod cachedMapperMethod(Method method) {
        MapperMethod mapperMethod = methodCache.get(method);
        if (mapperMethod == null) {
            mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
            methodCache.put(method, mapperMethod);
        }
        return mapperMethod;
    }
}
  1. 预编译的应用

MyBatis中使用了PreparedStatement预编译技术来提高SQL执行效率,它的代码实现如下所示:

public class PreparedStatementHandler extends BaseStatementHandler {
    
    public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        super(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    }
    
    protected Statement instantiateStatement(Connection connection) throws SQLException {
        String sql = boundSql.getSql();
        if (mappedStatement.getResultSetType() != null) {
            return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
        } else {
            return connection.prepareStatement(sql);
        }
    }
    
    protected void setParameters(PreparedStatement ps) throws SQLException {
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {
            for (int i = 0; i < parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    Object value;
                    String propertyName = parameterMapping.getProperty();
                    if (boundSql.hasAdditionalParameter(propertyName)) {
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
                        value = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        value = parameterObject;
                    } else {
                        MetaObject metaObject = configuration.newMetaObject(parameterObject);
                        value = metaObject.getValue(propertyName);
                    }
                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    JdbcType jdbcType = parameterMapping.getJdbcType();
                    if (value == null && jdbcType == null) {
                        jdbcType = configuration.getJdbcTypeForNull();
                    }
                    typeHandler.setParameter(ps, i + 1, value, jdbcType);
                }
            }
        }
    }
}

以上是MyBatis源码中的一些代码示例,涉及了工厂模式、代理模式、预编译等技术的应用。

 

MyBatis的缓存是一种用于提高数据访问性能的机制,它可以缓存查询结果,避免重复查询数据库,从而提高系统的响应速度和性能。MyBatis提供了两种类型的缓存:本地缓存和二级缓存。

  1. 本地缓存

本地缓存是MyBatis默认开启的缓存机制,它是基于PerpetualCache类实现的。PerpetualCache是一个基于HashMap实现的缓存容器,用于缓存查询结果。本地缓存的作用域是SqlSession级别的,即同一个SqlSession中的多个查询操作可以共享同一个缓存。本地缓存的缓存策略是LRU(Least Recently Used)策略,即当缓存容器空间不足时,会先将最近最少使用的缓存对象清理掉。

  1. 二级缓存

二级缓存是MyBatis的全局缓存机制,它可以缓存Mapper映射文件中定义的查询结果,以便不同的SqlSession之间可以共享同一个缓存。二级缓存的作用域是Mapper级别的,即同一个Mapper中的多个SqlSession可以共享同一个缓存。二级缓存的实现需要在Mapper映射文件中进行配置。MyBatis支持多种缓存实现方式,例如Ehcache、Redis等。二级缓存的缓存策略也是LRU策略。

需要注意的是,缓存虽然可以提高系统的性能,但也会带来一些问题,例如缓存数据的实时性问题、缓存数据的一致性问题等。因此,在使用缓存时需要根据具体的业务场景和需求,合理配置缓存策略,以确保系统的稳定性和运行效率。

 

MyBatis中常用的注解包括@Mapper、@Select、@Insert、@Update、@Delete、@Result、@Results、@Param等。

  1. @Mapper

@Mapper注解用于标识一个Mapper接口,表示这个接口是一个MyBatis的Mapper接口。通常情况下,@Mapper注解可以省略,因为MyBatis会在启动时自动扫描并注册所有的Mapper接口。

  1. @Select、@Insert、@Update、@Delete

@Select、@Insert、@Update、@Delete注解分别用于标识一个查询、插入、更新、删除操作的SQL语句。这些注解可以用在Mapper接口的方法上,也可以用在XML映射文件中的SQL语句上。

  1. @Result、@Results

@Result、@Results注解用于标识一个查询结果映射的结果集。@Result注解用于标识一个查询结果字段的映射关系,@Results注解用于标识多个查询结果字段的映射关系。这些注解可以用在Mapper接口的方法上,也可以用在XML映射文件中的resultMap元素上。

  1. @Param

@Param注解用于指定Mapper接口方法的参数名称,以便在SQL语句中引用该参数。如果Mapper接口方法只有一个参数,则可以省略@Param注解。如果Mapper接口方法有多个参数,则必须使用@Param注解来指定参数名称。

例如:

@Select("SELECT * FROM user WHERE id = #{id} AND name = #{name}")
User getUserByIdAndName(@Param("id") Integer id, @Param("name") String name);

需要注意的是,虽然使用注解可以简化Mapper接口的编写过程,但是过多的注解会使代码变得冗长和难以维护。因此,在编写Mapper接口时需要根据具体的业务场景和需求,合理选择使用注解或者XML映射文件来定义SQL语句和映射关系。

使用自动化生成工具可以大大简化MyBatis的开发工作,提高开发效率和代码质量。常见的MyBatis自动化生成工具包括MyBatis Generator、MyBatis Plus、JPA等。

  1. MyBatis Generator

MyBatis Generator是MyBatis官方提供的自动化代码生成工具,可以根据数据库表结构自动生成Mapper接口、XML映射文件和实体类。MyBatis Generator支持多种数据库,可以生成简单的CRUD操作,也可以生成复杂的查询语句。

  1. MyBatis Plus

MyBatis Plus是一个基于MyBatis的快速开发框架,提供了大量的便捷API和自动化代码生成工具。MyBatis Plus的自动化代码生成工具可以根据数据库表结构自动生成Mapper接口、实体类和XML映射文件,还支持代码模板自定义和生成策略配置。

  1. JPA

JPA是Java Persistence API的缩写,是Java EE标准中的一种ORM规范。JPA提供了一种简单、标准的方式来操作数据库,可以将Java对象映射到数据库表中。JPA的自动化代码生成工具可以根据实体类自动生成DAO接口和XML映射文件。

使用自动化生成工具可以减少开发人员的重复劳动,提高代码质量和可维护性。需要注意的是,在使用自动化生成工具时,需要根据具体的业务需求进行配置和定制,以满足系统的特定需求。同时,还需要注意自动生成的代码的质量和性能问题,需要进行适当的优化和调整。

 

 

下面是一个使用MyBatis Generator生成代码的例子。假设有一个数据库表user,包含idnameage三个字段,我们可以使用MyBatis Generator自动生成对应的Mapper接口、实体类和XML映射文件。

  1. 首先,在maven的pom.xml文件中添加MyBatis Generator插件的依赖:
<dependency>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-core</artifactId>
    <version>1.4.0</version>
</dependency>
  1. src/main/resources目录下创建一个名为generatorConfig.xml的配置文件,用于配置MyBatis Generator的相关参数。以下是一个简单的配置文件示例:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration PUBLIC
    "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
    "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <!-- 配置数据库连接信息 -->
    <context id="mysql" targetRuntime="MyBatis3">
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
            connectionURL="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=UTF-8&amp;useSSL=false"
            userId="root" password="123456">
        </jdbcConnection>

        <!-- 配置生成的Java模型类 -->
        <javaModelGenerator targetPackage="com.example.model"
            targetProject="src/main/java">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>

        <!-- 配置生成的Mapper接口 -->
        <sqlMapGenerator targetPackage="com.example.mapper"
            targetProject="src/main/java">
            <property name="enableSubPackages" value="true" />
        </sqlMapGenerator>

        <!-- 配置生成的Mapper XML文件 -->
        <javaClientGenerator type="XMLMAPPER"
            targetPackage="com.example.mapper" targetProject="src/main/java">
            <property name="enableSubPackages" value="true" />
        </javaClientGenerator>

        <!-- 配置生成的数据表 -->
        <table tableName="user" domainObjectName="User" />
    </context>
</generatorConfiguration>
  1. 在命令行中执行以下命令,即可根据上面的配置文件自动生成代码:
mvn mybatis-generator:generate
  1. 代码生成完成后,在com.example.model包下会生成一个名为User.java的Java模型类,表示数据库表user的一条记录;在com.example.mapper包下会生成一个名为UserMapper.java的Mapper接口,以及一个名为UserMapper.xml的XML映射文件,用于提供CRUD操作的方法。

 

以下是一个使用MyBatis Plus代码生成器生成实体类、Mapper接口和XML映射文件的例子:

  1. 首先,添加MyBatis Plus的代码生成器依赖到pom.xml文件中:
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.4.3.1</version>
</dependency>
  1. resources目录下创建一个名为generator.properties的配置文件,用于配置代码生成器的相关参数,例如:
# 数据库连接信息
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456

# 代码生成器配置
mybatis-plus.generator.outputDir=src/main/java
mybatis-plus.generator.author=yourname
mybatis-plus.generator.serviceName=%sService
mybatis-plus.generator.basePackage=com.example
mybatis-plus.generator.mapperPackage=com.example.mapper
mybatis-plus.generator.entityPackage=com.example.entity
mybatis-plus.generator.xmlPackage=mapper
mybatis-plus.generator.tableName=user
mybatis-plus.generator.enableCache=false
mybatis-plus.generator.activeRecord=true
mybatis-plus.generator.enableCache=false
mybatis-plus.generator.baseResultMap=true
mybatis-plus.generator.baseColumnList=true
  1. 创建一个名为CodeGenerator的类,用于执行代码生成器。以下是一个简单的代码生成器实现:
public class CodeGenerator {

    public static void main(String[] args) {
        // 读取配置文件
        Properties props = new Properties();
        try (InputStream in = CodeGenerator.class.getResourceAsStream("/generator.properties")) {
            props.load(in);
        } catch (IOException e) {
            throw new RuntimeException("读取配置文件失败", e);
        }

        // 创建代码生成器
        AutoGenerator generator = new AutoGenerator();

        // 全局配置
        GlobalConfig globalConfig = new GlobalConfig();
        globalConfig.setOutputDir(props.getProperty("mybatis-plus.generator.outputDir"));
        globalConfig.setAuthor(props.getProperty("mybatis-plus.generator.author"));
        globalConfig.setServiceName(props.getProperty("mybatis-plus.generator.serviceName"));
        globalConfig.setBasePackage(props.getProperty("mybatis-plus.generator.basePackage"));
        globalConfig.setActiveRecord(Boolean.parseBoolean(props.getProperty("mybatis-plus.generator.activeRecord")));
        globalConfig.setEnableCache(Boolean.parseBoolean(props.getProperty("mybatis-plus.generator.enableCache")));
        globalConfig.setBaseResultMap(Boolean.parseBoolean(props.getProperty("mybatis-plus.generator.baseResultMap")));
        globalConfig.setBaseColumnList(Boolean.parseBoolean(props.getProperty("mybatis-plus.generator.baseColumnList")));
        generator.setGlobalConfig(globalConfig);

        // 数据源配置
        DataSourceConfig dataSourceConfig = new DataSourceConfig();
        dataSourceConfig.setUrl(props.getProperty("spring.datasource.url"));
        dataSourceConfig.setDriverName(props.getProperty("spring.datasource.driver-class-name"));
        dataSourceConfig.setUsername(props.getProperty("spring.datasource.username"));
        dataSourceConfig.setPassword(props.getProperty("spring.datasource.password"));
        generator.setDataSource(dataSourceConfig);

        // 包配置
        PackageConfig packageConfig = new PackageConfig();
        packageConfig.setMapper(props.getProperty("mybatis-plus.generator.mapperPackage"));
        packageConfig.setEntity(props.getProperty("mybatis-plus.generator.entityPackage"));
        packageConfig.setXml(props.getProperty("mybatis-plus.generator.xmlPackage"));
        generator.setPackageInfo(packageConfig);

        // 策略配置
        StrategyConfig strategyConfig = new StrategyConfig();
        strategyConfig.setNaming(NamingStrategy.underline_to_camel);
        strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);
        strategyConfig.setEntityLombokModel(true);
        strategyConfig.setRestControllerStyle(true);
        strategyConfig.setInclude(props.getProperty("mybatis-plus.generator.tableName"));
        generator.setStrategy(strategyConfig);

        // 执行代码生成器
        generator.execute();
    }
}

以上代码会根据generator.properties文件中配置的参数来生成代码,并将生成的代码保存在src/main/java目录下的相应包中。

需要注意的是,代码生成器会覆盖已有的代码文件,因此在执行代码生成器之前请确保备份好原有代码文件,以免数据丢失。

在MyBatis中,#$都是用于替换SQL语句中的参数的占位符。它们的主要区别在于替换的方式和安全性。

#是预编译的占位符,它会将传入的参数值放入一个预编译语句中,然后将整个语句发送到数据库执行。这种方式会将参数值进行转义,可以防止SQL注入漏洞。例如:

<select id="getUserById" resultType="User">
  SELECT * FROM user WHERE id = #{id}
</select>

在执行这个SQL语句时,#{id}会被替换成一个预编译占位符,例如?,然后将整个语句发送到数据库执行。这种方式可以防止SQL注入漏洞,因为传入的参数值会被转义。

$是字符串替换的占位符,它会直接将传入的参数值替换到SQL语句中,然后将整个语句发送到数据库执行。这种方式不会对传入的参数值进行转义,容易导致SQL注入漏洞。例如:

<select id="getUserByName" resultType="User">
  SELECT * FROM user WHERE name = '${name}'
</select>

在执行这个SQL语句时,${name}会被替换成传入的参数值,例如'admin',然后将整个语句发送到数据库执行。如果传入的参数值恶意构造了SQL注入漏洞,那么就会导致安全问题。

为了防止SQL注入漏洞,可以采用以下措施:

  1. 尽可能使用#占位符,而不是$占位符,减少SQL注入漏洞的风险。
  2. 对于必须使用$占位符的情况,可以对传入的参数值进行校验和转义,例如调用StringEscapeUtils.escapeSql()方法对参数值进行转义。
<select id="getUserByName" resultType="User">
  SELECT * FROM user WHERE name = '${name}'
</select>
public User getUserByName(String name) {
    String safeName = StringEscapeUtils.escapeSql(name);
    String sql = "SELECT * FROM user WHERE name = '" + safeName + "'";
    // ...
}
  1. 在配置文件中开启MyBatis的自动映射驼峰命名规则,这样即使有人恶意构造了SQL注入漏洞,也不会对数据库造成损害。可以在配置文件中添加以下配置:
<settings>
  <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

上述配置会将数据库中下划线命名风格的列名自动映射到Java中的驼峰命名风格。例如,数据库中的user_name列会自动映射为Java中的userName属性。这样即使有人恶意构造了SQL注入漏洞,也不会对userName属性造成影响。

撞库是指攻击者使用大量的用户名和密码组合来尝试登录系统,以此来猜测正确的用户名和密码。为了防止撞库攻击,可以采取以下措施:

  1. 强化密码策略:要求用户设置强密码,并定期更改密码,可以设置密码长度、复杂度、有效期等规则。建议采用多因素认证方式来增强安全性。
  2. 限制登录尝试次数:可以限制用户在一段时间内登录失败的次数,超出限制则暂时锁定账户。这可以有效防止撞库攻击。但需要注意不能设置过于严格的限制,否则可能会影响用户的正常登录。
  3. 监控异常登录行为:可以监控系统的登录行为,例如登录时间、地点、设备等信息,如果发现异常登录行为,可以及时采取措施,例如发送警报、暂时禁止该账户等。
  4. 使用IP限制:可以限制用户只能从特定的IP地址范围内登录,这可以有效防止来自未知来源的登录尝试。但需要注意不能设置过于严格的限制,否则可能会影响用户的正常登录。
  5. 对敏感操作进行审计:对系统中的敏感操作进行审计,例如登录、修改密码、查询用户信息等,可以及时发现异常操作行为,帮助系统管理员快速采取措施。
  6. 定期更新系统和应用程序:定期更新系统和应用程序可以及时修复已知的漏洞,提高系统的安全性。

综上所述,防止撞库攻击需要采用多种措施,包括强化密码策略、限制登录尝试次数、监控异常登录行为、使用IP限制、对敏感操作进行审计等。此外,还需要定期更新系统和应用程序,提高系统的安全性。

 

  1. 强化密码策略:为了防止撞库攻击,可以要求用户设置强密码,并定期更改密码,可以设置密码长度、复杂度、有效期等规则。例如,要求密码长度不少于8个字符,包含大小写字母、数字和特殊字符,有效期不超过90天等。此外,建议采用多因素认证方式来增强安全性,例如使用手机短信验证码、令牌、指纹识别等。
  2. 限制登录尝试次数:攻击者通常会使用自动化脚本进行撞库攻击,如果限制用户在一段时间内登录失败的次数,超出限制则暂时锁定账户,这可以有效防止撞库攻击。例如,可以设置在一分钟内最多允许用户登录失败3次,超出限制则暂时锁定账户10分钟等。但需要注意不能设置过于严格的限制,否则可能会影响用户的正常登录。
  3. 监控异常登录行为:可以通过监控系统的登录行为,例如登录时间、地点、设备等信息,如果发现异常登录行为,可以及时采取措施,例如发送警报、暂时禁止该账户等。例如,如果用户在短时间内多次从不同的国家或地区尝试登录,则可能是攻击者在进行撞库攻击。
  4. 使用IP限制:可以限制用户只能从特定的IP地址范围内登录,这可以有效防止来自未知来源的登录尝试。例如,可以设置只允许公司内部的IP地址登录系统,拒绝来自外部网络的访问。但需要注意不能设置过于严格的限制,否则可能会影响用户的正常登录。
  5. 对敏感操作进行审计:对系统中的敏感操作进行审计,例如登录、修改密码、查询用户信息等,可以及时发现异常操作行为,帮助系统管理员快速采取措施。例如,如果发现某个账户在短时间内多次尝试修改密码,则可能是攻击者在进行撞库攻击。
  6. 定期更新系统和应用程序:定期更新系统和应用程序可以及时修复已知的漏洞,提高系统的安全性。例如,定期升级操作系统、数据库、Web服务器等应用程序,及时安装安全补丁,可以减少系统被攻击的风险。

总之,为了防止撞库攻击,需要采用多种措施,包括强化密码策略、限制登录尝试次数、监控异常登录行为、使用IP限制、对敏感操作进行审计等。此外,还需要定期更新系统和应用程序,提高系统的安全性。最好通过综合多种措施来提高系统的安全性,以确保系统能够有效地抵御撞库攻击。

 


0 条评论

发表回复

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

蜀ICP备16001794号
© 2014 - 2024 linpxing.cn All right reserved.