在没有ORM的时代,我们直接从数据库中返回完整的数据集,然后通过键值对取出相应字段的数据,当然更要需要,取出的数据还要转换成编程语言相应的类型,再进行数据的操作。这种方式存储数据干脆直接,还可以利用Sql语句进行查询的优化,有些复杂的查询逻辑还可以编写成存储过程,充分利用数据库的性能,降低客户端的压力。
//ADO.NET示例
string connectionString =
"Data Source=(local);Initial Catalog=Northwind;"
+ "Integrated Security=true";
// Provide the query string with a parameter placeholder.
string queryString =
"SELECT ProductID, UnitPrice, ProductName from dbo.products "
+ "WHERE UnitPrice > @pricePoint "
+ "ORDER BY UnitPrice DESC;";
// Specify the parameter value.
int paramValue = 5;
// Create and open the connection in a using block. This
// ensures that all resources will be closed and disposed
// when the code exits.
using (SqlConnection connection =
new SqlConnection(connectionString))
{
// Create the Command and Parameter objects.
SqlCommand command = new SqlCommand(queryString, connection);
command.Parameters.AddWithValue("@pricePoint", paramValue);
// Open the connection in a try/catch block.
// Create and execute the DataReader, writing the result
// set to the console window.
try
{
connection.Open();
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
Console.WriteLine(" {0} {1} {2}",
reader[0], reader[1], reader[2]);
}
reader.Close();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadLine();
}
}
但直接利用Sql返回数据的方式,有个最大的问题就是可读性太差。首先程序代码中有很多拼合SQL的代码,以及数据迭代的代码。因为没有限制,还可以一边查询,一边处理业务逻辑,数据库操作代码和客户端代码混合一起,数量一上来,就变成天书了。所以有聪明的人,就把一些比较通用的部分分离出来,比如构建一个DBHelper,把数据库连接部分单独拉出来,再提供一些通用的查询函数,微软的企业库就是一个比较典型的实现。但这样也只是可以稍微偷懒一点,事务上好控制点。
//利用EntLib返回一个数据集。
Database db = DatabaseFactory.CreateDatabase("ConStr");
string sql = "select * from Brand";
DbCommand cmd = db.GetSqlStringCommand(sql);
DataSet set = db.ExecuteDataSet(cmd);
后来有了J2EE框架,里边定义了各种Bean,有一种Bean叫Entity Bean,这个Entity Bean基本上跟数据库一一对应的,可序列化,可用于远程传递数据的无状态类。也就是说J2EE里边,要求事先将数据记录转换成存值的对象,再通过Session Bean(也就是Bussiness Object、BO)的调用返回数据的Copy,所有的数据库存取的代码,限制在了持久层(也就是DataAccess Layer,DataAccess Object、DAO),按照这种思维一起,应用程序的结构就很清晰了,代码的可读性也提升了,代码的可维护性也就增强了,然后就可以往系统中增加更多的功能了。
虽说数据库操作放在了持久层里,但数据对像的生成还是要程序员去写的,一个表有上百个字段,那你就得写上几倍于数据表字段数量的get/set,以前直接写Sql是根据需要来做Select的投影,需要什么就查什么,而现在则要全部做个映射,这个工作量可想而知还是不小的。于是有人把这部分操作弄成了一个独立的组件,开放出相应的接口给下游程序调用,当然目标是Sql无关的,带来的好处就是干净,少了很多数据库层面的拼装。
//EntityFramework的一个例子
public abstract class QueryBase<T> : IQueryBase<T> where T : EntityBase
{
protected AppDataContext DataContext { get; set; }
public List<T> FindByPage()
{
return DataContext.Set<T>().ToList();
}
}
对应的Sql
select * from T --T must be replaced by actual table name
ORM还有个特性就是实现数据库的无关性。上述Entity Framework的例子里,因为不需要写出Sql的具体实现,所以可以通过规范,使用不同的数据库驱动,实现同一套代码,在不同的数据库上运行。
在Setup.cs中设置数据库配置。
services.AddDbContext<AppDataContext>(
options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))
);
在Jpa中指定数据库方言
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws IOException, PropertyVetoException, SQLException {
LocalContainerEntityManagerFactoryBean efactory = new LocalContainerEntityManagerFactoryBean();
efactory.setDataSource(dataSource());
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
jpaVendorAdapter.setGenerateDdl(true);
jpaVendorAdapter.setDatabase(Database.MYSQL);
jpaVendorAdapter.setShowSql(true);
efactory.setJpaVendorAdapter(jpaVendorAdapter);
efactory.setPersistenceProviderClass(org.hibernate.jpa.HibernatePersistenceProvider.class);
efactory.setPersistenceUnitName("test");
efactory.setLoadTimeWeaver(new InstrumentationLoadTimeWeaver());
return efactory;
}
ORM另一个目的就是就是数据操作面向对象话,消除了面向过程的Sql语句的干扰,编程语言就可以做许多神奇的事情来。比如springdata中,只需要定义接口,然后通过动态代码加上依赖注入的方式,就可以实现持久层的实现:
public interface AccountRepository extends JpaRepository<Account, String> {
Account findByUsername(String username);
}
当然ORM也有很多不足,比如无形中增加了许多配置项,也增加了学习成本,查询性能上没法跟SQL相比,聚合查询不太容易实现等。