MyBatis是一款流行的ORM框架,其源码实现是非常值得学习的。以下是一些可以借鉴的思维方式:
- 设计模式的应用:MyBatis的源码中应用了许多常见的设计模式,例如工厂模式、代理模式、模板方法模式等。这些设计模式的应用使得代码更加灵活、可扩展、易于维护。
- 面向对象的思维方式:MyBatis的源码中充分体现了面向对象的思维方式,例如封装、继承、多态等。通过合理的抽象和封装,使得代码的可读性和可维护性得到了极大的提高。
- 性能优化的思维方式:MyBatis的源码中考虑了大量的性能优化策略,例如缓存、批量操作、预编译等。这些优化策略可以在其他Java项目中借鉴和应用,提高系统的性能和效率。
- 代码组织和架构设计的思维方式:MyBatis的源码非常规范和清晰,代码组织和架构设计也非常合理。通过合理的模块划分和代码组织,使得代码的可读性、可维护性和可扩展性得到了很好的保证。
- 单元测试和持续集成的思维方式:MyBatis的源码中有大量的单元测试和持续集成的相关实践,这些实践可以帮助Java工程师更好地保证代码的质量和稳定性。
以下是一些MyBatis源码中的代码示例,以供Java工程师参考:
- 工厂模式的应用
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);
}
}
}
- 代理模式的应用
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;
}
}
- 预编译的应用
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提供了两种类型的缓存:本地缓存和二级缓存。
- 本地缓存
本地缓存是MyBatis默认开启的缓存机制,它是基于PerpetualCache类实现的。PerpetualCache是一个基于HashMap实现的缓存容器,用于缓存查询结果。本地缓存的作用域是SqlSession级别的,即同一个SqlSession中的多个查询操作可以共享同一个缓存。本地缓存的缓存策略是LRU(Least Recently Used)策略,即当缓存容器空间不足时,会先将最近最少使用的缓存对象清理掉。
- 二级缓存
二级缓存是MyBatis的全局缓存机制,它可以缓存Mapper映射文件中定义的查询结果,以便不同的SqlSession之间可以共享同一个缓存。二级缓存的作用域是Mapper级别的,即同一个Mapper中的多个SqlSession可以共享同一个缓存。二级缓存的实现需要在Mapper映射文件中进行配置。MyBatis支持多种缓存实现方式,例如Ehcache、Redis等。二级缓存的缓存策略也是LRU策略。
需要注意的是,缓存虽然可以提高系统的性能,但也会带来一些问题,例如缓存数据的实时性问题、缓存数据的一致性问题等。因此,在使用缓存时需要根据具体的业务场景和需求,合理配置缓存策略,以确保系统的稳定性和运行效率。
MyBatis中常用的注解包括@Mapper、@Select、@Insert、@Update、@Delete、@Result、@Results、@Param等。
- @Mapper
@Mapper注解用于标识一个Mapper接口,表示这个接口是一个MyBatis的Mapper接口。通常情况下,@Mapper注解可以省略,因为MyBatis会在启动时自动扫描并注册所有的Mapper接口。
- @Select、@Insert、@Update、@Delete
@Select、@Insert、@Update、@Delete注解分别用于标识一个查询、插入、更新、删除操作的SQL语句。这些注解可以用在Mapper接口的方法上,也可以用在XML映射文件中的SQL语句上。
- @Result、@Results
@Result、@Results注解用于标识一个查询结果映射的结果集。@Result注解用于标识一个查询结果字段的映射关系,@Results注解用于标识多个查询结果字段的映射关系。这些注解可以用在Mapper接口的方法上,也可以用在XML映射文件中的resultMap元素上。
- @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等。
MyBatis Generator是MyBatis官方提供的自动化代码生成工具,可以根据数据库表结构自动生成Mapper接口、XML映射文件和实体类。MyBatis Generator支持多种数据库,可以生成简单的CRUD操作,也可以生成复杂的查询语句。
- MyBatis Plus
MyBatis Plus是一个基于MyBatis的快速开发框架,提供了大量的便捷API和自动化代码生成工具。MyBatis Plus的自动化代码生成工具可以根据数据库表结构自动生成Mapper接口、实体类和XML映射文件,还支持代码模板自定义和生成策略配置。
- JPA
JPA是Java Persistence API的缩写,是Java EE标准中的一种ORM规范。JPA提供了一种简单、标准的方式来操作数据库,可以将Java对象映射到数据库表中。JPA的自动化代码生成工具可以根据实体类自动生成DAO接口和XML映射文件。
使用自动化生成工具可以减少开发人员的重复劳动,提高代码质量和可维护性。需要注意的是,在使用自动化生成工具时,需要根据具体的业务需求进行配置和定制,以满足系统的特定需求。同时,还需要注意自动生成的代码的质量和性能问题,需要进行适当的优化和调整。
下面是一个使用MyBatis Generator生成代码的例子。假设有一个数据库表user
,包含id
、name
和age
三个字段,我们可以使用MyBatis Generator自动生成对应的Mapper接口、实体类和XML映射文件。
- 首先,在maven的
pom.xml
文件中添加MyBatis Generator插件的依赖:
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.4.0</version>
</dependency>
- 在
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&characterEncoding=UTF-8&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>
- 在命令行中执行以下命令,即可根据上面的配置文件自动生成代码:
mvn mybatis-generator:generate
- 代码生成完成后,在
com.example.model
包下会生成一个名为User.java
的Java模型类,表示数据库表user
的一条记录;在com.example.mapper
包下会生成一个名为UserMapper.java
的Mapper接口,以及一个名为UserMapper.xml
的XML映射文件,用于提供CRUD操作的方法。
以下是一个使用MyBatis Plus代码生成器生成实体类、Mapper接口和XML映射文件的例子:
- 首先,添加MyBatis Plus的代码生成器依赖到
pom.xml
文件中:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.3.1</version>
</dependency>
- 在
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
- 创建一个名为
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注入漏洞,可以采用以下措施:
- 尽可能使用
#
占位符,而不是$
占位符,减少SQL注入漏洞的风险。 - 对于必须使用
$
占位符的情况,可以对传入的参数值进行校验和转义,例如调用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 + "'";
// ...
}
- 在配置文件中开启MyBatis的自动映射驼峰命名规则,这样即使有人恶意构造了SQL注入漏洞,也不会对数据库造成损害。可以在配置文件中添加以下配置:
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
上述配置会将数据库中下划线命名风格的列名自动映射到Java中的驼峰命名风格。例如,数据库中的user_name
列会自动映射为Java中的userName
属性。这样即使有人恶意构造了SQL注入漏洞,也不会对userName
属性造成影响。
撞库是指攻击者使用大量的用户名和密码组合来尝试登录系统,以此来猜测正确的用户名和密码。为了防止撞库攻击,可以采取以下措施:
- 强化密码策略:要求用户设置强密码,并定期更改密码,可以设置密码长度、复杂度、有效期等规则。建议采用多因素认证方式来增强安全性。
- 限制登录尝试次数:可以限制用户在一段时间内登录失败的次数,超出限制则暂时锁定账户。这可以有效防止撞库攻击。但需要注意不能设置过于严格的限制,否则可能会影响用户的正常登录。
- 监控异常登录行为:可以监控系统的登录行为,例如登录时间、地点、设备等信息,如果发现异常登录行为,可以及时采取措施,例如发送警报、暂时禁止该账户等。
- 使用IP限制:可以限制用户只能从特定的IP地址范围内登录,这可以有效防止来自未知来源的登录尝试。但需要注意不能设置过于严格的限制,否则可能会影响用户的正常登录。
- 对敏感操作进行审计:对系统中的敏感操作进行审计,例如登录、修改密码、查询用户信息等,可以及时发现异常操作行为,帮助系统管理员快速采取措施。
- 定期更新系统和应用程序:定期更新系统和应用程序可以及时修复已知的漏洞,提高系统的安全性。
综上所述,防止撞库攻击需要采用多种措施,包括强化密码策略、限制登录尝试次数、监控异常登录行为、使用IP限制、对敏感操作进行审计等。此外,还需要定期更新系统和应用程序,提高系统的安全性。
- 强化密码策略:为了防止撞库攻击,可以要求用户设置强密码,并定期更改密码,可以设置密码长度、复杂度、有效期等规则。例如,要求密码长度不少于8个字符,包含大小写字母、数字和特殊字符,有效期不超过90天等。此外,建议采用多因素认证方式来增强安全性,例如使用手机短信验证码、令牌、指纹识别等。
- 限制登录尝试次数:攻击者通常会使用自动化脚本进行撞库攻击,如果限制用户在一段时间内登录失败的次数,超出限制则暂时锁定账户,这可以有效防止撞库攻击。例如,可以设置在一分钟内最多允许用户登录失败3次,超出限制则暂时锁定账户10分钟等。但需要注意不能设置过于严格的限制,否则可能会影响用户的正常登录。
- 监控异常登录行为:可以通过监控系统的登录行为,例如登录时间、地点、设备等信息,如果发现异常登录行为,可以及时采取措施,例如发送警报、暂时禁止该账户等。例如,如果用户在短时间内多次从不同的国家或地区尝试登录,则可能是攻击者在进行撞库攻击。
- 使用IP限制:可以限制用户只能从特定的IP地址范围内登录,这可以有效防止来自未知来源的登录尝试。例如,可以设置只允许公司内部的IP地址登录系统,拒绝来自外部网络的访问。但需要注意不能设置过于严格的限制,否则可能会影响用户的正常登录。
- 对敏感操作进行审计:对系统中的敏感操作进行审计,例如登录、修改密码、查询用户信息等,可以及时发现异常操作行为,帮助系统管理员快速采取措施。例如,如果发现某个账户在短时间内多次尝试修改密码,则可能是攻击者在进行撞库攻击。
- 定期更新系统和应用程序:定期更新系统和应用程序可以及时修复已知的漏洞,提高系统的安全性。例如,定期升级操作系统、数据库、Web服务器等应用程序,及时安装安全补丁,可以减少系统被攻击的风险。
总之,为了防止撞库攻击,需要采用多种措施,包括强化密码策略、限制登录尝试次数、监控异常登录行为、使用IP限制、对敏感操作进行审计等。此外,还需要定期更新系统和应用程序,提高系统的安全性。最好通过综合多种措施来提高系统的安全性,以确保系统能够有效地抵御撞库攻击。
0 条评论